instituto de ciências matemáticas e de computação - usp departamento de...

124
Instituto de Ciências Matemáticas e de Computação - USP Departamento de Ciências da Computação e Estatística Tópicos em Linguagens de Programação Objetivo: Introduzir e discutir os conceitos relacionados a Linguagens de Programação, principalmente no contexto de linguagens imperativas, e apresentar as principais características dos demais modelos (paradigmas) de Linguagens de Programação. A quem se destina: alunos das disciplinas de Linguagens de Programação Organizado por: Profa. Maria das Graças Volpe Nunes ([email protected]) Publicado por: Katti Faceli ([email protected])

Upload: dinhdang

Post on 15-Dec-2018

216 views

Category:

Documents


0 download

TRANSCRIPT

Instituto de Ciências Matemáticas e de Computação - USP

Departamento de Ciências da Computação e Estatística

Tópicos em Linguagens de Programação

Objetivo: Introduzir e discutir os conceitos relacionados a Linguagens de Programação, principalmente no contexto de linguagens imperativas, e apresentar as principais características dos demais modelos (paradigmas) de Linguagens de Programação.

A quem se destina: alunos das disciplinas de Linguagens de Programação

Organizado por: Profa. Maria das Graças Volpe Nunes

([email protected])

Publicado por: Katti Faceli

([email protected])

1

Índice 1. Definição .............................................................................................................................................. 4 2. Porque estudar LP ?.............................................................................................................................. 4 3. Histórico............................................................................................................................................... 4

3.1. FORTRAN (FORmula TRANslation) ............................................................................................. 5 3.2. COBOL (COmmon Business Oriented Language).......................................................................... 6 3.3. ALGOL 60 (ALGorithmic Oriented Language) .............................................................................. 8 3.4. LISP (LISt Processing) .................................................................................................................... 9 3.5. APL (A Programming Language).................................................................................................. 10 3.6. BASIC (Beginners All-purpose Symbolic Instruction Code)....................................................... 10 3.7. PL/I (Programming Language I).................................................................................................... 11 3.8. SIMULA 67 ................................................................................................................................... 12 3.9. ALGOL 68..................................................................................................................................... 13 3.10. PASCAL.................................................................................................................................... 13 3.11. PROLOG (PROgramming in LOGic) ....................................................................................... 14 3.12. SMALL TALK.......................................................................................................................... 15 3.13. C ................................................................................................................................................ 16 3.14. MÓDULA 2............................................................................................................................... 16 3.15. ADA .......................................................................................................................................... 17

4. Especificação de uma LP ................................................................................................................... 18 4.1. Sintaxe de uma LP ......................................................................................................................... 18 4.2. Semântica de LP ............................................................................................................................ 22

5. Tradução de uma LP .......................................................................................................................... 22 5.1. Interpretação .................................................................................................................................. 22 5.2. Compilação .................................................................................................................................... 22

6. Características de Design de LP......................................................................................................... 23 7. Escolha de uma LP............................................................................................................................. 24 8. Paradigmas de LP............................................................................................................................... 25

8.1. O Paradigma Imperativo de LP...................................................................................................... 25 8.1.1. Binding.................................................................................................................................. 25

8.1.1.1. "Information Binding" ...................................................................................................... 25 8.1.1.2. Escopo de Binding e Unidades de Execução .................................................................... 28 8.1.1.3. Escopo de Binding de Nome............................................................................................. 30 8.1.1.4. Escopo de Binding de Locação......................................................................................... 32 8.1.1.5. Exercícios.......................................................................................................................... 34

8.1.2. Estruturas de controle............................................................................................................ 34 8.1.2.1. Condicional Simples ......................................................................................................... 34 8.1.2.2. Condicional Com 2 Alternativas....................................................................................... 35 8.1.2.3. Condicional Multi-alternativas ......................................................................................... 36

8.1.2.4. Condicional Não-Determinística ...................................................................................... 37 8.1.2.5. Iteração Infinita................................................................................................................. 38 8.1.2.6. Iteração Pré-Teste ............................................................................................................. 38 8.1.2.7. Iteração Pós-Teste............................................................................................................. 38 8.1.2.8. Iteração In-Teste ............................................................................................................... 39 8.1.2.9. Iteração com Número Fixo de Passos ............................................................................... 40 8.1.2.10. Iteração Não-Determinística......................................................................................... 41 8.1.2.11. Desvio Incondicional.................................................................................................... 41

8.1.3. Tipos de Dados...................................................................................................................... 42

2

8.1.3.1. Tipo Numérico - Inteiro .................................................................................................... 44 8.1.3.2. Tipo Numérico - Real ....................................................................................................... 45 8.1.3.3. Tipo Boolean..................................................................................................................... 45 8.1.3.4. Tipo Ponteiro .................................................................................................................... 46 8.1.3.5. Tipos Enumerados (domínio criado pelo usuário)............................................................ 48 8.1.3.6. Tipos de Dados Compostos ..............................................................................................49

8.1.3.6.1. Array ........................................................................................................................ 49 8.1.3.6.2. Records ou Structures .............................................................................................. 52 8.1.3.6.3. Records Variantes .................................................................................................... 52 8.1.3.6.4. Strings ...................................................................................................................... 54 Arquivos (Files)......................................................................................................................... 55 8.1.3.6.6. Conjuntos (Sets) ....................................................................................................... 55 8.1.3.6.7. Exercícios................................................................................................................. 56

8.1.4. Procedimentos e Funções ...................................................................................................... 59 8.1.4.1. O ambiente local ............................................................................................................... 61 8.1.4.2. O ambiente global ............................................................................................................. 62

8.1.4.2.1. Escopo Estático ........................................................................................................ 62 8.1.4.2.2. Escopo Dinâmico ..................................................................................................... 63

8.1.4.3. O ambiente de parâmetros ................................................................................................ 64 8.1.4.3.1. Parâmetros de Entrada (IN)...................................................................................... 65 8.1.4.3.2. Parâmetros de Entrada e Saída (IN/OUT)................................................................ 65 8.1.4.3.3. Parâmetros de Saída (OUT) ..................................................................................... 66 8.1.4.3.4. Procedimentos como parâmetros ............................................................................. 66 8.1.4.3.5. Passagem de Parâmetro "Por Nome" ....................................................................... 67

8.1.4.4. Funções ou Value-Returning Procedures (VRPs)............................................................. 69 8.1.4.5. Overloading de Procedimentos ......................................................................................... 69 8.1.4.6. Co-rotinas.......................................................................................................................... 70 8.1.4.7. Exercícios.......................................................................................................................... 71

8.1.5. Tipo Abstrato de Dados (TAD)............................................................................................. 71 8.1.5.1. Definição........................................................................................................................... 71 8.1.5.2. Implementação.................................................................................................................. 73 8.1.5.3. Parametrização de Tipos de Dados ................................................................................... 75 8.1.5.4. TAD em ADA................................................................................................................... 77 8.1.5.5. TAD em MÓDULA 2....................................................................................................... 79 8.1.5.6. TAD em C......................................................................................................................... 79 8.1.5.7. Exercícios.......................................................................................................................... 79

8.1.6. Tratamento de Exceções........................................................................................................ 80 8.1.6.1. Tipos de exceções: ............................................................................................................ 81 8.1.6.2. Habilitação & Desabilitação de Exceções ........................................................................ 81 8.1.6.3. Fluxo de controle quando do término de um Handler ...................................................... 83 8.1.6.4. Propagação de Exceção .................................................................................................... 84 8.1.6.5. Tratamento de Exceções em ADA.................................................................................... 84 8.1.6.6. Exercícios.......................................................................................................................... 84

8.1.7. Concorrência ......................................................................................................................... 85 8.1.7.1. Definição........................................................................................................................... 85 8.1.7.2. Chamada ........................................................................................................................... 86 8.1.7.3. Compartilhamento de Dados e Comunicação Inter-Processsos........................................ 86

8.1.7.3.1. Mail Model............................................................................................................... 87 8.1.7.3.2. Phone Model ............................................................................................................ 88

3

8.1.7.4. Sincronização.................................................................................................................... 89 8.1.7.4.1. Token ....................................................................................................................... 89 8.1.7.4.2. Gate .......................................................................................................................... 89

8.1.7.5. Concorrência em ADA ..................................................................................................... 90 8.1.7.5.1. Compartilhamento de Dados.................................................................................... 90 8.1.7.5.2. Esperas Seletivas...................................................................................................... 92 8.1.7.5.3. Guardas .................................................................................................................... 93

8.1.7.6. Exercícios.......................................................................................................................... 94 8.2. O Paradigma Funcional de LP ....................................................................................................... 96

8.2.1. O Paradigma Funcional......................................................................................................... 96 8.2.2. FP: uma linguagem funcional pura (Backus-78)................................................................... 97 8.2.3. LISP (LISt Processing) ....................................................................................................... 103

8.2.3.1. Componentes básicos...................................................................................................... 103 8.2.3.2. Aglumas Funções Pré-definidas ..................................................................................... 105 8.2.3.3. Definição de Funções...................................................................................................... 106 8.2.3.4. Exercícios........................................................................................................................ 110

8.2.4. Comparação de LISP com FP ............................................................................................. 110 8.3. O Paradigma Lógico de LP.......................................................................................................... 112 8.4. O Paradigma Orientado a Objeto (OO) de LP ............................................................................. 113

8.4.1. Componentes básicos: ......................................................................................................... 113 8.4.2. Propriedades do Modelo Orientado a Objeto ...................................................................... 113 8.4.3. Exemplo em Hool: .............................................................................................................. 114 8.4.4. Exemplo em C++: ............................................................................................................... 117 8.4.5. Orientação a Objeto (OO) x Paradigma Imperativo (PI) .................................................... 122 8.4.6. Exercícios............................................................................................................................ 122

9. Bibliografia ...................................................................................................................................... 123

4

1. Definição Uma LP (Linguagem de Programação) é uma linguagem destinada a ser usada por uma pessoa

para expressar um processo através do qual um computador pode resolver um problema. Os quatro modelos de LP correspondem aos pontos de vista dos quatro componentes citados. A eficiência na construção e execução de programas depende da combinação dos quatro pontos de vista.

2. Porque estudar LP ? 1. Maior habilidade em resolver problemas: uma maior compreensão de uma LP pode aumentar nossa

habilidade em pensar em como atacar os problemas. Tanto melhor se dominarmos os vários modelos de LP.

2. Melhor uso de uma LP: compreensão das funções e implementação das estruturas de uma LP nos levam a usar a LP de modo a extrair o máximo de sua funcionalidade e eficiência.

3. Melhor escolha de uma LP: adequação ao problema.

4. Maior facilidade em aprender novas LPs: conceitos chaves comuns às LPs.

5. Melhor designer de LPs: linguagens de interfaces de sistemas, extensão de LP via operadores e tipos de dados.

3. Histórico Veja um pouco da história de algumas linguagens de programação que introduziram conceitos

importantes para as futuras LPs e que ainda estão em uso. Essas linguagens estão classificadas em três períodos, de acordo com a época em que surgiram.

1955 - 1965

• FORTRAN (FORmula TRANslation) • COBOL (COmmon Business Oriented Language) • ALGOL 60 (ALGorithmic Oriented Language) • LISP (LISt Processing) • APL (A Programming Language) • BASIC (Beginners All-purpose Symbolic Instruction Code)

1965 - 1971 (LP's baseadas em ALGOL)

• PL/I (Programming Language I) • SIMULA 67 • ALGOL 68 • PASCAL

Linguagens dos anos 80 (criadas na década de 70)

• PROLOG (PROgramming in LOGic) • SMALL TALK

5

• C • MODULA 2 • ADA

3.1. FORTRAN (FORmula TRANslation)

A linguagem Fortran, desenvolvida em 1956 por John Backus, foi proposta visando a resolução de problemas científicos, para isto utilizando a notação algébrica. Foi desenvolvida, inicialmente para uma máquina específica, o IBM 704. É, ainda hoje, uma linguagem muito utilizada no meio técnico-científico, tendo sido aprimorada ao longo do tempo, constituindo as diversas versões disponíveis. Um dos motivos pelos quais o FORTRAN é ainda muito utilizado é a disponibilidade de uma vasta biblioteca de software contendo rotinas freqüentemente utilizadas, tais como rotinas para cálculo de funções trigonométricas, equações algébricas polinomiais, etc, o que permite uma redução dos custos e tempo de desenvolvimento dos programas.

Contribuições para futuras linguagens:

• variáveis • comando de atribuição • conceito de tipos • modularidade (com o uso de subprogramas) • E/S formatadas

Exemplo de programa FORTRAN: * Programa que converte um valor decimal, maior ou igual a zero, * lido através do console do sistema, em seu correspondente valor * binário, imprmindo este valor na tela do console. * Autores: José Carlos Maldonado * Ronaldo Luiz Dias Cereda * Data: Abril/87 * - Programa Principal - * Declaracao de variaveis, constantes simbolicas e iniciializacoes INTEGER CONT, J, LIMITE,NUMERO, VETRES(8) PARAMETER (LIMITE = 255) DATA VETRES/8*0/ * Leitura e impressão do valor decimal WRITE(*,*) 'DIGITE O VALOR DECIMAL' READ(*,*) NUMERO WRITE(*,10) NUMERO 10 FORMAT(1X,//,1X,'VALOR DECIMAL FORNECIDO: ',I3) * Verificacao do limite IF (NUMERO.GT.LIMITE) $THEN WRITE(*,*) 'NUMERO MUITO GRANDE ELSE * Chamada da rotina para conversao e impressao dos resultados CONT = 0 CALL CONV(NUMERO, VETRES, CONT)

WRITE(*,20) (VETRES(J), J=CONT, 1, -1) 20 FORMAT(1X, 'VALOR BINARIO CORRESPONDENTE: ', 8I2)

6

ENDIF STOP

END * Sub-rotina que faz a conversao de um numero decimal, maior * ou igual a zero, em um numero binario * Parametro de entrada * NUM: valor a ser convertido * Parametros de saida * VETRES: vetor que contem o resultado * PONT: numero de bits do resultado

SUBROUTINE CONV(NUM, VETRES, PONT) * Declaracao dos parametros

INTEGER NUM, PONT, VETRES(8) * Declaracao de variavel local

INTEGER AUX * conversao 100 IF (NUM.GE.2) THEN * inicio do enquanto PONT = PONT + 1 AUX = NUM/2 VETRES(PONT) = NUM - AUX*2 NUM = AUX GOTO 100 * fim do enquanto ENDIF PONT = PONT + 1 VETRES(PONT) = NUM RETURN END

3.2. COBOL (COmmon Business Oriented Language)

A linguagem COBOL, desenvolvida em 1959 pelo Departamento de Defesa dos EUA e fabricantes de computadores , é padrão para as aplicações comerciais e muito utilizada ainda hoje. Seu desenvolvimento se deu de forma independente da máquina. O código é "English-like" e é excelente para a manipulação de arquivos.

Contribuições de COBOL para futuras linguagens:

• código mais legível • estrutura de dados heterogênea (record)

Exemplo de programa COBOL:

Organização de um programa COBOL: Um programa COBOL possui quatro divisões que devem ser colocadas na ordem adequada. A sua estrutura completa é: IDENTIFICATION DIVISION PROGRAM-ID. nome-do-programa

7

[AUTHOR. [comentários] ...] [INSTALLATION. [comentários] ...] [INSTALLATION. [comentários] ...] [DATE-WRITTEN. [comentários] ...] [DATE-COMPILED. [comentários] ...] [SECURITY. [comentários] ...] [REMARKS. [comentários] ...] ENVIRONMENT DIVISION [CONFIGURATION SECTION. SOURCE-COMPUTER. OBJECT-COMPUTER. [SPECIAL-NAMES. ]]. [INPUT-OUTPUT SECTION. FILE-CONTROL. [I-O-CONTROL. ENTRADA.]]. DATA DIVISION. [FILE SECTION. { Descrição dos arquivos { Descrição dos registros } ... } ... ]. [WORKING-STORAGE SECTION. [Descrição dos itens de dados] ... [Descrição de registros] ... ]. [LINKAGE SECTION. [Descrição dos itens de dados] ... [Descrição de registros] ... ]. [COMMUNICATION SECTION. {Descrição das Entradas de Comunicação. [Descrição das Entradas de Registros] ... } ... ]. [REPORT SECTION. {Descrição de relatórios} {Descrição dos grupos de relatórios} ... } ... ]. PROCEDURE DIVISION. [USING identificador-1 [identificador-2] ...] [DECLARATIVES. {nome-da-seção SECTION. USE SENTENCE. {nome-de-parágrafo. {sentença} ... } ... } ... END DECLARATIVES.] {nome-da-seção SECTION [prioridade] - ] {nome-de-parágrafo. {sentença} ... } ... } ...

Exemplo: 001 010 IDENTIFICATION DIVISION. 001 020 PROGRAM-ID. TABSORT. 001 030 AUTHOR. ALEX BASTOS. 001 040 INSTALLATION. RDC-DIV USUARIOS. 001 050 DATE-WRITEN. 18/12/72. 001 060 DATE-COMPILED. 20/12/72. 001 070 REMARKS. Este programa grava arquivo sequencial em ordem crescente e classifica-o apos em ordem decrescente. 001 100 ENVIRONMENT DIVISION. 001 110 CONFIGURATION SECTION. 001 120 SOURCE-COMPUTER. IBM-370-165. 001 130 OBJECT-COMPUTER. IBM-370-165. 001 140 INPUT-OUTPUT SECTION

8

001 150 FILE-CONTROL. 001 160 SELECT ARQUIVO ASSIGN TO DA-S-DISCO. 001 170 SELECT TRABALHO ASSIGN TO DA-S-SORTWK01. 001 180 DATA DIVISION. 001 190 FILE SECTION. 001 200 FD ARQUIVO LABEL RECORDS ARE STANDARD 002 010 DATA RECORD IS ENTRADA. 002 020 01 ENTRADA. 002 030 02 X PIC 99. 002 040 02 Y PIC X(10). 002 050 02 FILLER PIC X(20). 002 060 SD TRABALHO LABEL RECORDS ARE STANDARD 002 070 DATA RECORD IS TRAB. 002 080 01 TRAB. 002 090 02 Z PIC 99. 002 100 02 K PIC X(10). 002 110 02 FILLER PIC X(20). 002 120 WORKING-STORAGE SECTION. 002 130 77 I PIC 99 VALUE ZEROS. 002 140 PROCEDURE DIVISION. 002 150 OPEN OUTPUT ARQUIVO. 002 160 MOVE 'TESTE-SORT' TO Y. 002 170 GRAVACAO. 002 180 MOVE I TO X. 002 190 ADD 1 TO I. 002 200 WRITE ENTRADA. 003 010 IF I 100 GO TO GRAVACAO. 003 020 SORT TRABALHO DESCENDING Z USING 003 030 ARQUIVO GIVING ARQUIVO. 003 040 OPEN INPUT ARQUIVO. 003 050 GRAVA. 003 060 READ ARQUIVO AT END GO TO FIN. 003 070 DISPLAY X' Y. 003 080 GO TO GRAVA. 003 090 FIN. 003 100 CLOSE ARQ. 003 110 STOP RUN.

3.3. ALGOL 60 (ALGorithmic Oriented Language)

Linguagem algébrica de origem européia, desenvolvida pelo comitê Internacional popular, destinada à resolução de problemas científicos. Influenciou o projeto de quase todas as linguagens projetadas a partir de 1960. Descrita em BNF (Backus-Naur Form), foi projetada independente da implementação, o que permite uma maior criatividade, porém de implementação mais difícil. É pouco usada em aplicações comerciais devido à ausência de facilidades de E/S na descrição e pelo pouco interesse de vendedores. Além disso, tornou-se padrão para a publicação de algoritmos.

Contribuições de ALGOL 60 para futuras linguagens:

• estrutura de blocos: habilidade de se criar blocos de comandos para o escopo de variáveis e extensão de influência de comandos de controle

• comandos de controle estruturados: if-then-else e uso de uma condição geral para

controle de iteração • recursividade: habilidade de um procedimento chamar a si próprio

Exemplo de programa ALGOL 60:

Procedimento que calcula raiz quadrada. procedure EQ2OR (A, B, C, z1r, z1i, z2r, z2i, INDETERMINATE); value A, B, C; real A, B, C, z1r, z1i, z2r, z2i; label INDETERMINATE; begin real discriminant;

if A ≠ 0 then go to normal; ir B = 0 then go to INDETERMINATE; z1r := z2r := -C/B; go to set zero;

normal: discriminant := B 2 - 4 x A x C; if discriminant 0 then go to real solution; complex: z1r := z2r := -B/2/A; z1i := sqrt(-discriminant)/2/A; z2i := - z1i; go to finis; real solution: z1r := (-B+(if B 0 then -1 else 1)xsqrt(discriminant))/2/A; z2r := C/A/z1r; set zero: z1i := z2i := 0; finis: end EQ2OR

3.4. LISP (LISt Processing)

Linguagem funcional criada em 1960, por John McCartly do grupo de IA do MIT, para dar suporte à pesquisa em Inteligência Artificial. Foi inicialmente desenvolvida para o IBM 704. Existem muitos dialetos pois LISP nunca foi padronizada. Porém, em 1981 surgiu o Common LISP que é um padrão informal. Os programas em LISP são listas.

Contribuição de LISP para futuras linguagens:

• é pioneira na idéia de computação simbólica ou não-numérica

Exemplo de programa LISP:

Organização de um programa LISP: Um programa utiliza a lista encadeada como estrutura de dados básica. Sendo uma linguagem funcional, ao invés de especificar operações como um conjunto sequencial de comandos, invoca funções e composição de funções para especificar múltiplas ações. Utiliza a mesma estrutura para dados e programas.

Predicado que testa se dois conjuntos tem algum elemento em comum. Retorna nil se os dois conjuntos são disjuntos. (DEFUN INTERSECTP (A B)

(COND ((NULL A) NIL) ((MEMBER (CAR A) B)) (T (INTERSECTP (CDR A) B))))

9

3.5. APL (A Programming Language)

Foi desenvolvida por volta de 1960 por Kenneth Iverson - Harvard, IBM. Utiliza notação matemática, com operadores poderosos, possuindo muitos operadores e muitos caracteres o que gera grande dificuldade de implementação. Tem uma notação compacta e é utilizada em aplicações matemáticas. Segue o modelo funcional e tem como principal estrutura de dados o ARRAY, com diversos operadores sobre esta estrutura.

Exemplo de programa APL:

Função que eleva ao cubo o seu argumento, caso ele seja negativo, e ao quadrado, se ele for positivo.

3.6. BASIC (Beginners All-purpose Symbolic Instruction Code)

A linguagem BASIC, desenvolvida em meados dos anos 60 por John Kemeny e Thomas Kurtz no Dartmouth College, teve como objetivo ensinar alunos de graduação a usarem um ambiente interativo de programação, através de uma LP de fácil aprendizado. Com o surgimento dos microcomputadores de baixo custo, no início dos anos 70, o BASIC tornou-se muito popular, embora não tenha contribuído muito tecnologicamente.

Contribuições de Basic para futuras linguagens:

• uma das primeiras LPs a prover um ambiente de programação interativo como parte da linguagem

• execução interpretativa de programas

Exemplo de programa Basic

Organização de um programa BASIC: Um programa é constituído de uma seqüência de declarações (instruções) que devem aparecer na ordem em que deverão ser executadas, a menos que seja indicado um desvio. A seguir são apresentadas regras que se aplicam a todas as declarações em BASIC:

1. Cada declaração deve aparecer em uma linha separada. 2. Uma declaração não pode possuir comprimento superior a um linha (em geral 80 caracteres). 3. Cada declaração deve ser iniciada com uma quantidade positiva inteira, conhecida como

número da declaração (número da linha). Duas declarações não podem possuir o mesmo número de identificação.

4. Declarações sucessivas devem possuir números de declarações crescentes. 5. Os números das declarações devem ser seguidos de uma palavra-chave do BASIC, que indicará

o tipo de instrução a ser executado.

10

11

6. Espaços em branco podem ser inseridos, sempre que se desejar, para melhorar a leitura da declaração.

Obs: Cada linha em branco devem possuir um único número de declaração seguido de um espaço em branco.

Exemplo: 10 REM ANAGRAMA PARA QUATRO LETRAS 20 PRINT "FORNECA QUATRO LETRAS QUAISQUER:" 30 PRINT 40 INPUT L$(1), L$(2), L$(3), L$(4) 50 PRINT 60 FOR I1 = 1 TO 4 70 FOR I2 = 1 TO 4 80 IF I2 = I1 THEN 150 90 FOR I3 = 1 TO 4 100 IF I3 = I1 THEN 140 110 IF I3 = I2 THEN 140 120 LET I4 = 10 - (I1 + I2 + I3) 130 PRINT L$(I1); L$(I2); L$(I3); L$(I4) 140 NEXT I3 150 NEXT I2 160 NEXT I1 170 END RUN

3.7. PL/I (Programming Language I)

Desenvolvida em meados dos anos 60 pela IBM com o objetivo de incorporar características das LPs existentes numa única LP de propósito geral. Assim PL/I inclui:

• estrutura de bloco, de controle e recursividade do ALGOL 60; • subprogramas e E/S formatadas do FORTRAN; • manipulação de arquivos e registros do COBOL; • alocação dinâmica de memória e estruturas encadeadas do LISP; • operações de arrays do APL.

É uma linguagem difícil de aprender e implementar devido a sua grande complexidade. Além disso, faz uso de defaults.

Contribuições de PL/I para futuras linguagens:

• tratamento de interrupção - execução de procedimentos específicos quando uma condição excepcional ocorre

• multitarefa - especificação de tarefas que podem ser executadas concorrentemente

Exemplo de programa PL/I:

Programa que calcula média aritmética. MEDIA: PROCEDURE OPTIONS (MAIN);

DECLARE (N, M, X, SOMA, MEDIA) FLOAT;

12

SOMA = 0; MEDIA = 0; GET LIST (N); M = N; DO WHILE (N 0);

GET LIST (X); SOMA = SOMA + X; N = N - 1;

END; MEDIA = SOMA / M; PUT LIST ('MEDIA ARITMETICA = ', MEDIA);

END MEDIA;

3.8. SIMULA 67

Linguagem baseada em Algol 60, criada no início dos anos 60 por Ole Johan Dahl e Kristan Nygaard, na Noruega. É destinada à descrição de sistemas e programação de simulações.

Contribuição de SIMULA 67 para futuras linguagens:

• conceito de classe: Uma classe é um encapsulamento de dados e procedimentos que podem ser instanciados em um conjunto de objetos. É o conceito predecessor ao tipo abstrato de dados (ADA e Módula 2) e das classes das linguagens orientadas a objeto (Smalltalk e C++)

Exemplo de programa Simula 67:

Classe para manipulação de números complexos. class complex(x, y); real x, y; begin ref(complex) procedure add (c); ref(complex) c; if c =/= none then add :- new complex(x + c.x, y + c.y); ref(complex) procedure sub (c); ref(complex) c; if c =/= none then sub :- new complex(x - c.x, y - c.y); ref(complex) procedure mult (c); ref(complex) c; if c =/= none then mult :- new complex(x * c.x - y * c.y, x * c.y + y * c.x); ref(complex) procedure div (c); ref(complex) c; begin real m; if c =/= none then begin m := c.modulus; if m ¬= 0.0 then div :- mult(c.conjugate).stretch(1/m); end; end ***div***; ref(complex) procedure conjugate; conjugate :- new complex(x, -y); real procedure modulus; modulus := sqrt(x*x + y*y); ref(complex) procedure stretch(k); real k; stretch :- new complex(k*x, k*y); procedure write; begin outchar('('); outfix(x, 3, 12);

13

outchar(','); outfix(y, 3, 12); outchar(')'); end ***write*** end ***complex***

3.9. ALGOL 68

É muito diferente do Algol 60. Linguagem de propósito geral que foi projetada para a comunicação de algoritmos, para sua execução eficiente em vários computadores e para ajudar seu ensino a estudantes. Porém é de difícil descrição, o que resultou em uma baixa popularidade.

Contribuição de ALGOL 68 para futuras linguagens:

• ortogonalidade: uma LP que é ortogonal tem um número de construtores básicos e um conjunto de regras para combiná-los relativamente pequeno (oposto a PL/I)

Exemplo de programa ALGOL 68:

Programa que lê três palavras de dez caracteres cada e as mostra em ordem alfabética. begin

[10]char s1, s2, s3; read((s1, s2, s3)); if s1 < s2 then if s2 < s3

then print((s1, s2, s3)) elif s1 < s3 then print((s1, s3, s2)) else print((s3, s1, s2)) fi

elif s1 < s3 then print((s2, s1, s3)) elif s2 < s3 then print((s2, s3, s1)) else print((s3, s2, s1))

fi end

3.10. PASCAL

Desenvolvida por Niklaus Wirth em 1969, é uma linguagem de fácil aprendizado e implementação, suporta programação estruturada e é adequada para o ensino de programação. Em meados dos anos 80 também passou a ser usada para a programação em micro-computadores. Influenciou praticamente todas as linguagens mais recentes.

Contribuições de Pascal para futuras linguagens:

• estruturas de controle flexíveis

14

• tipos definidos pelo usuário • arquivos • records • conjuntos

Exemplo de programa Pascal: PROGRAM ordena(input, output); (* Este programa ordena um vetor de n números reais, em ordem crescente *) VAR n, i, inic: 1..100; x: ARRAY [1..100] OF real; temp: real; PROCEDURE troca; (* Troca, do maior ao menor, os elementos de um vetor *) BEGIN

FOR inic := 1 TO n-1 DO FOR i := inic + 1 TO n DO

IF x[i] < x[inic] THEN BEGIN

temp := x[inic]; x[inic] := x[i]; x[i] := temp;

END; END; (* troca *) BEGIN (* bloco principal *)

write('Quantos sao os numeros? '); readln(n); FOR i := 1 TO n DO BEGIN

write('x[', i:3, ']= ? '); readln(x[i]);

END; troca; writeln; writeln('Dados ordenados:'); writeln; FOR i := 1 TO n DO

writeln('x[', i:3, ']= ', x[i]:4:1); END.

3.11. PROLOG (PROgramming in LOGic)

Linguagem desenvolvida em 1972 em Marseille na França. É destinada a aplicações de Inteligência Artificial e se baseia em lógica formal. É a LP do projeto japonês de quinta geração.

Exemplo de programa PROLOG:

Predicado que retorna verdadeiro se e somente se o argumento é uma data válida. date(Year, Month, Day) :-

Month 0, Month < 13,

15

mdays(Year, Month, Ndays), Day 0, Day =< Ndays.

mdays(_, 1, 31). mdays(Y, 2, 28) :-

not leap(Y). mdays(Y, 2, 29) :-

leap(Y). mdays(_, 3, 31). mdays(_, 4, 30). mdays(_, 5, 31). mdays(_, 6, 30). mdays(_, 7, 31). mdays(_, 8, 31). mdays(_, 9, 30). mdays(_, 10, 31). mdays(_, 11, 30). mdays(_, 12, 31). leap(Y) :-

divides(4, Y), not divides(100, Y).

leap(Y) :- divides(400, Y).

divides(Div, K) :- 0 is K mod Div.

3.12. SMALL TALK

Criada por Alan Kay da Xerox - Palo Alto no início dos anos 70. Apresenta um ambiente de programação com menus pop-up, windows e mouse (modelo para Apple Macintosh). Segue o modelo orientado a objetos, possuindo o conceito de classe do SIMULA 67 mais encapsulamento, herança e instanciação.

Contribuições de SmallTalk para futuras linguagens:

• primeira LP a utilizar o paradigma de programação interativa • introduz o conceito de LP extensível

Exemplo de programa SmallTalk:

Classe que representa o objeto Linha. Class Line |startPoint endPoint| [

from: sPoint to: ePoint startPoint <- sPoint. endPoint <- ePoint

| + offset ^ Line new; from: (startPoint + offset) to: (endPoint + offset)

|

16

drawWith: APen aPen up. aPen goTo: startPoint. aPen down. aPen goTo: endPoint

]

3.13. C

Desenvolvida pelo Bell Lab no início dos anos 70, visando a implementação do UNIX. Possui um padrão feito por Kernighan e Ritchie em 1978. Tem facilidades para a programação em "baixo nível" e gera código eficiente. Possui um grande conjunto de operadores, o que permite um código compacto, porém de baixa legibilidade. É excelente para construir programas portáveis.

Exemplo de programa C: main()n /* count lines in input */ {

int c, nl; nl = 0; while ((c = getchar()) != EOF)

if (c == '\n') ++nl;

printf("%d\n", nl); }

3.14. MÓDULA 2

Criada por Niklaus Wirth no final dos anos 70, é uma linguagem de propósito geral, baseada em melhorias no Pascal. É boa para projetos de desenvolvimento de software de grande porte. Além disso foi usada para ensinar programação. Módula 2 elaborou Pascal em:

• módulos podem ser usados para implementar TAD (Tipos Abstratos de Dados) • todas as estruturas de controle têm uma palavra-chave de terminação • co-rotinas - execução intercalada • tipos de procedimentos

Exemplo de programa Módula 2:

Programa que lista todas as possíveis permutações de n objetos distintos a[1] ... a[n]. MODULE Permute; FROM InOut IMPORT Read, Write, WriteLn; VAR n: CARDINAL; ch: CHAR; a: ARRAY[1..20] OF CHAR; PROCEDURE output; VAR i: CARDINAL; BEGIN FOR i := 1 TO n DO Write(a[i])END; WriteLn

17

END output; PROCEDURE permute(k: CARDINAL); VAR i: CARDINAL; t: CHAR; BEGIN IF k = 1 THEN output ELSE permute(k-1); FOR i := 1 TO k-1 DO t := a[i]; a[i] := a[k]; a[k] := t; permute(k-1); t := a[i]; a[i] := a[k]; a[k] := t; END END END permute BEGIN Write(""); n := 0; Read(ch); WHILE ch "" DO n := n + 1; a[n] := ch; Write(ch); Read(ch); END; WriteLn; permute(n) END permute.

3.15. ADA

Foi desenvolvida no início dos anos 70 pelo Departamento de Defesa dos Estados Unidos. É dedicada aos "embedded systems" (operam como parte de um sistema maior) e se baseia no Pascal. Teve um padrão em 1983. Além disso, usa conceitos de classe do Simula 67, adota o tratamento de exceções de PL/I e provê facilidades para processamento concorrente. Foi projetada para apoiar aplicações numéricas, programação de sistemas e aplicações que envolvem considerações de tempo real e concorrência. Seu nome se deve a ADA Augusta, 1a programadora, colaboradora de Charles Babbage - século 19.

Exemplo de programa ADA

Prática de tiro ao alvo. with I_O_PACKAGE; procedure TARGET_PRATICE is

use I_O_PACKAGE; -- This program reads in four values, respectively representing -- the initial X and Y velocities of a projectile, the distance -- to a target, and the height of the target. -- It prints a message indicating whether the projectile will -- hit the target or not. G: constant FLOAT := 9.81; -- meters per second per second X_VELOCITY, Y.VOLOCITY, TARGET_DISTANCE, TARGET_HEIGHT, NET_RISE: FLOAT; procedure COMPUTE_RISE(V_X, V_Y, DISTANCE: in FLOAT; RISE: out FLOAT) is

TIME: FLOAT; begin

TIME := DISTANCE / V_X; RISE := V_Y*TIME - (G/2.0)*(TIME**2);

end; begin

18

PUT_LINE("ENTER X AND Y VELOCITIES, DISTANCE, AND HEIGHT:"); GET(X_VELOCITY); GET(Y_VELOCITY); GET(TARGET_DISTANCE); GET(TARGET_HEIGHT); COMPUTE_RISE(X_VELOCITY, Y_VELOCITY, TARGET_DISTANCE, NET_RISE); if NET_RISE 0.0 and NET_RISE < TARGET_HEIGHT then

PUT("HIT"); else

PUT("MISS"); end if;

end;

4. Especificação de uma LP

A descrição de uma linguagem de programação envolve dois aspectos: sintaxe e semântica. A sintaxe é o conjunto de regras que determinam quais construções são corretas e a semântica é a descrição de como as construções sintaticamente corretas são interpretadas ou executadas.

Ex: a := b (Pascal)

• comando de atribuição correto (sintaxe) • substitua valor de a com o valor atual de b (semântica)

4.1. Sintaxe de uma LP

A sintaxe de uma LP é descrita por uma Gramática que tem como elementos básicos:

1. Conjunto de Símbolos Terminais (T): aparecem nas construções da LP; um conjunto de caracteres.

2. Conjunto de Símbolos Não Terminais (NT): não presentes na LP; nomes de categorias sintáticas definidas pelas produções. Notação: < .

3. Conjunto de Produções (P): definições de símbolos não terminais. Forma: <NT ::= {T U NT}*.

4. Símbolo Inicial (S): um dos NT.

Exemplo: Gramática de uma linguagem de calcular em BNF (Backus Naur Form):

P: <cálculo> ::= <expressão> = <expressão> ::= <valor> | <valor><operador><expressão> <valor> ::= <número> | <sinal><número> <número> ::= <semsinal> | <semsinal>.<semsinal> <semsinal> ::= <dígito> | <dígito><semsinal> <dígito>::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 <sinal> ::= + | - <operador> ::= + | - | / | *

19

--- T, --- NT, --- Metalinguagem

S = <cálculo>

Na notação anterior, poderíamos expressar: opcionalidade [ ], repetição { } e alternância |. O uso desses 3 metasímbolos estende a BNF para a notação EBNF (Extended BNF).

Rescrevendo os NT <expressão>, <valor>, <semsinal. e <número>:

<expressão> ::= <valor> [<operador><expressão>] <valor> ::= [<sinal>] <semsinal> [.<semsinal>] <semsinal> ::= <dígito> {<dígito>}

PS: <número> pode ser eliminado.

Uma Gramática pode ser usada para gerar sentenças válidas e para verificar se uma seqüência de símbolos é válida.

20

Gerando sentença válida:

Cadeia Atual Produção Aplicada

S = <cálculo> 1

<expressão> = 3

<valo><operador><expressão> = 4

<número><operador><expressão> = 6

<semsinal><operador><expressão> = 9

<dígito><semsinal><operador><expressão> = 12

2<semsinal><operador><expressão> = 8

2<dígito><operador><expressão> = 15

25<operador><expressão> = 24

25 * <expressão> = 2

25 * <valor> = 4

25 * <número> = 7

25 * <semsinal>.<semsinal> = 8

25 * <dígito>.<semsinal> = 11

25 * 1.<semsinal> = 8

25 * 1.<dígito> = 15

25 * 1.5 = cadeia final

21

Verificando se 6 + 3 / 12 = é um cálculo:

6 + 3 / 1 2 =

<dígito> <operador> <dígito> <operador> <dígito> <dígito> |

<semsinal> | <semsinal> | | <semsinal> |

<número> | <número> | <semsinal> |

<valor> | <valor> | <número> |

| | | | <valor> |

| | | | <expressão |

| | <expressão> |

<expressão> |

<cálculo>

Redução de uma cadeia inválida (6 * =):

6 * =

<dígito> <operador> |

<semsinal> | |

<número> | |

<valor> | |

? ? ?

Gramáticas que podem ser descritas em BNF e EBNF são conhecidas como Gramáticas Livres de Contexto: definições de NT são independentes do contexto em que o NT aparece.

A maioria das LPs não são Livres de Contexto - elas contém algumas regras sensíveis ao contexto. Ex: "variável deve ser declarada antes de ser usada". São, assim, chamadas linguagens

sensíveis ao contexto. Os métodos formais para reconhecer linguagens sensíveis ao contexto são bastante complexos. Na prática, especifica-se uma LP como livre de contexto e trata-se a sensibilidade ao contexto de maneira informal.

4.2. Semântica de LP

A semântica de uma LP, que corresponde a como esta LP será implementada, pode ser descrita de maneira formal, porém, em geral, é descrita de maneira informal. Esta apresentação dará um enfoque operacional ao problema, com o objetivo de fornecer uma visão dos problemas encontrados quando da implementação de linguagens.

5. Tradução de uma LP

Existem dois métodos para se traduzir um programa escrito em uma determinada linguagem de programação para a linguagem de máquina: Interpretação e Compilação.

5.1. Interpretação

Um interpretador traduz o programa fonte um comando por vez e chama uma rotina para executar esse comando. A vantagem é que o interpretador não traduz comandos que podem não ser executados e pode relatar erros na linguagem original em cada ponto de execução. Na prática as linguagens interpretadas servem para a realização de uma prototipagem rápida.

5.2. Compilação

Um Compilador traduz o programa fonte inteiro, produzindo um outro programa equivalente, em linguagem executável. A vantagem é que o compilador precisa traduzir um comando apenas uma única vez, não importando quantas vezes ele será executado. Na prática o compilador é usado para gerar o código definitivo (eficiente) de um programa.

22

O processo de compilação:

Fases Principais da Compilação:

6. Características de Design de LP

O principal objetivo de uma LP é auxiliar o programador no processo de desenvolvimento de software. Isso inclui auxílio no projeto, implementação, teste, verificação e manutenção do software. Algumas características que contribuem para isso são:

• Simplicidade:

• Nível semântico: número mínimo de conceitos e estruturas. Os conceitos devem ser naturais, de fácil aprendizado, com pouco risco de má interpretação.

• Nível sintático: cada conceito deve ter representação única e "clara" - evitar sintaxe muito concisa, pois isto é contraprodutivo para inteligibilidade. Deve-se excluir múltiplas representações e representações confusas.

23

24

• Abstração: apenas aspectos relevantes dos objetos. Tem reflexo nas tarefas de design, implementação e modificação de programas.

• Nível de dados: programador pode trabalhar mais efetivamente usando abstrações mais simples, que não incluam detalhes irrelevantes dos dados.

• Nível de procedimentos: facilita modularidade e boas práticas de design.

• Expressividade: facilidade com que um objeto pode ser representado. As LPs devem permitir uma representação natural de objetos e procedimentos (por exemplo, estruturas de dados e de controle apropriadas). Simplicidade e Expressividade são características um tanto antagônicas e a maior aplicação de uma ou outra depende do domínio de aplicação do problema. Deste modo deve-se restringir os problemas a domínios específicos.

• Ortogonalidade: refere-se à integração entre os conceitos, o grau de interação entre diferentes conceitos, e como eles podem ser combinados de maneira consistente. Por exemplo, quando uma "string" não pode ser passada como parâmetro, os conceitos de "string" e "parâmetro" não podem interagir, e portanto, há falta de ortogonalidade. Além disso, se uma LP usa o operador := para atribuição de "inteiro" e <- para atribuição de "string", então o conceito "atribuição interage inconsistentemente com os de "inteiro" e "string". A ortogonalidade reduz o número de exceções de regras e torna a LP mais fácil de ser aprendida. Também leva a dificuldades: combinações difíceis de se implementar ou mesmo improváveis de ocorrer.

• Portabilidade: movimento de um programa de uma máquina a outra. Utilização de um padrão independente de máquina.

7. Escolha de uma LP

A escolha de uma linguagem de programação deve basear-se em 7 critérios básicos:

• implementação

• disponibilidade quanto à plataforma • eficiência: velocidade de execução do programa objeto

• competência na LP

• experiência do programador • competência do grupo envolvido

• portabilidade

• necessidade de rodar em diferentes máquinas

• sintaxe

• certos tipos de aplicação acomodam-se melhor em certas sintaxes

• semântica

• aplicação X facilidades • por exemplo, para processamento concorrente pode-se usar ADA, para utilização de

recursividade pode-se usar Pascal

25

• ambiente de programação

• ferramentas para desenvolvimento de software diminuem o esforço de programação • bibliotecas

• modelo de computação

• aplicação X modelo de computação • por exemplo, para realização de busca heurística é adequado o modelo lógico, para

simulações, o modelo orientado a objeto

8. Paradigmas de LP

Os paradigmas, ou também chamados modelos, de linguagens de programação são:

• Paradigma Imperativo • Paradigma Funcional • Paradigma Lógico • Paradigma Orientado a Objetos

8.1. O Paradigma Imperativo de LP

O modelo Imperativo é baseado na perspectiva do computador: a execução seqüencial de comandos e o uso de dados são conceitos baseados no modo como os computadores executam programas no nível de linguagem de máquina. Este modelo é o predominante. As LPs imperativas são de fácil tradução. Um programa imperativo é equivalente a uma seqüência de modificações de locações de memória.

Linguagens Imperativas: FORTRAN, COBOL, ALGOL 60, APL, BASIC, PL/I, SIMULA 67, ALGOL 68, PASCAL, C, MODULA 2, ADA.

8.1.1. Binding

8.1.1.1. "Information Binding"

As LPs imperativas imitam as ações dos computadores ao nível de linguagem de máquina (LM). Nesse nível, os computadores operam com 2 unidades principais: CPU e memória. Uma unidade de execução típica em LM consiste de 4 passos:

1 - Obter o endereço de locações para um resultado e 1 ou mais operandos 2 - Obter o dado operando da(s) locação(ões) do operando 3 - Computar o dado resultado dos dados operandos 4 - Armazenar o dado resultado na locação do resultado

Exemplo:

A := B + C seria executado como:

1 - Obter os endereços de A, B e C

2 - Obter os dados dos endereços B e C 3 - Computar o resultado de B + C 4 - Armazenar o resultado na locação de A

Apesar da abstração de endereços em nomes, as LPs imperativas mantém os 4 passos como uma unidade de programa padrão. Essa unidade de execução se tornou a unidade fundamental de LP imperativas - e é chamada de comando de atribuição (em BNF: <nome> <op_atribuição> <expressão>).

De fundamental importância para a performance dessa atribuição é o estabelecimento e uso de um número de bindings ("ligações"):

passo 1: binding entre nomes e locações de operandos e resultados

passo 2: binding entre locação e valor para estabelecer valores de operandos

passo 4: binding entre locação do resultado e o valor computado

No passo 3, a computação depende da interpretação dos dados e dos operadores definidos. LPs relacionam dados e operadores através de tipos. Veremos o papel de tipo em binding. Além disso será discutido o Escopo dos bindings: definição e mudança de bindings. Dados objetos & Bindings

Dado objeto = (L, N, V, T)

onde L - locação, N - nome, V - valor, T - tipo

Binding é a atribuição de valor a um dos quatro componentes acima. Esses bindings podem ser alterados em certas ocasiões.

Os bindings podem ocorrer em tempo de compilação, em tempo de loading (quando o programa LM gerado pelo compilador está sendo alocado à locações específicas na memória), ou em tempo de execução.

Bindings de locação geralmente ocorrem em tempo de loading, mas também podem ocorrer em

26

tempo de execução (variáveis em procedimentos e alocação dinâmica).

Bindings de nome ocorrem tipicamente em tempo de compilação, quando uma declaração é encontrada. (em arrays e records ocorrem bindings compostos de nomes).

Bindings de tipo ocorrem geralmente em tempo de compilação, através de declarações de tipo. Veja exemplos dos efeitos da declaração de tipos em ADA nas figuras seguintes.

Binding de tipo dinâmico ocorre durante tempo de execução, não havendo declarações. O tipo do dado objeto é determinado pelo tipo do valor, e portanto, uma mudança de valor implica em novo binding.

Exemplos de binding em ADA:

a) A : integer;

27

b) B : integer := 0;

c) C : constant integer := 0;

8.1.1.2. Escopo de Binding e Unidades de Execução

Divisões de um programa:

• programa - maior divisão; unidade executável fundamental • blocos • comando - menor divisão; unidade indivisível

O objetivo de se juntar unidades de execução, neste caso, é para identificar o escopo de bindings.

Comandos:

28

29

• unidade de controle fundamental de LPs imperativas • menor unidade traduzível • comandos compostos (condicionais, iterativos)

Em LPs como LISP e PROLOG, os comandos possuem um único formato:

LISP - (nome_função (par1 ... parn) <corpo>)

PROLOG - predicado (arg1, ..., argn) :- <corpo>.

A maioria das LPs imperativas possuem várias sintaxes para tipos diferentes de comandos.

Referência a comandos: labels

<label> : <comando>

Binding - tempo de compilação (Pascal, ADA, FORTRAN)

- tempo de execução (SNOBOL, APL)

Variações: labels podem ser:

• obrigatórios - BASIC • opcionais - Pascal, ADA, FORTRAN, C • inteiros - Pascal, FORTRAN, BASIC • identificadores - ADA, C • declarados explicitamente - Pascal • declarados implicitamente - ADA, FORTRAN • detectados por posição - FORTRAN • detectados por notação - Pascal, C

Delimitação de Comandos:

• um comando por linha - FORTRAN • marca delimitadora - ADA, C, Pascal (em ADA e C ';' termina comandos e em Pascal

separa comandos) Pascal:

... x := x + 1 else ...

ADA, C: ... x := x + 1; else ...

Blocos: conjunto de comandos para atender a determinado fim.

1 - Escopo de estrutura de controle

30

Estruturas condicionais

Estruturas iterativas

2 - Escopo de procedimentos ou funções

3 - Unidades de compilação

Blocos compilados separadamente e depois unidos para execução

4 - Escopo de bindings

Bloco de comandos sobre os quais bindings específicos são válidos

8.1.1.3. Escopo de Binding de Nome

Blocos que definem um escopo de binding de nome contém, em geral, duas partes:

1. Uma seção de DECLARAÇÕES, que define os bindings que valem dentro do bloco

2. Uma seção EXECUTÁVEL, que contém os comandos do bloco, onde valem os bindings

Sintaticamente, isso requer delimitadores. Ex:

... BLOCK A;

DECLARE I; BEGIN A ... {I de A} - binding válido END A; ... Num bloco, dois tipos de bindings:

local - feito por declarações no bloco

não-local - feito por declaração fora do bloco.

Blocos aninhados: program P; declare X; begin P ... {X de P} block A; declare Y; begin A ... {X de P; Y de A} block B; declare Z; begin B ... {X de P; Y de A; Z de B}

31

end B; ... {X de P; Y de A} end A; ... {X de P} block C; declare Z; begin C ... {X de P; Z de C} end C; ... {X de P} end.

Política do Escopo Léxico ou Estático:

1. Se um nome tem uma declaração num bloco, este nome é ligado ao objeto especificado na declaração.

2. Se um nome não tem declaração num bloco, ele é ligado ao mesmo objeto ao qual ele foi ligado no bloco que contém o bloco atual, no texto do programa. Se o bloco não está contido em outro, ou se o nome não foi ligado no bloco que contém este, então o nome não está ligado a qualquer objeto no bloco atual.

"Hole-in-scope" - Situação em que um bloco redeclara um nome já ligado no ambiente que o contém. Neste caso, a declaração local se sobrepõe ao binding não-local, fazendo o objeto ligado não-localmente inacessível no bloco atual.

Ex:

program P; declare X, Y; begin P ... {X de P; Y de P} block A; declare X, Z; begin A ... {X de A; Z de A; Y de P; X de P é inacessível em A} end A ... {X de P; Y de P} end P;

Escopo Estático x Escopo Dinâmico

Escopo Dinâmico: O ambiente "global" de uma unidade (procedure) é o ambiente da unidade que a chamou. Isto é, uma procedure herda como ambiente global aquele da unidade que a chamou e, portanto, este só pode ser determinado em tempo de execução.

Contornando o "Hole-in-scope":

32

ADA - via sintaxe: no exemplo, P.X refere-se ao objeto X ligado em P, enquanto X é o objeto ligado em A.

8.1.1.4. Escopo de Binding de Locação

Hipótese assumida: binding de locação feito em tempo de loading, permanecendo válido durante toda a execução do programa.

Efeito colateral: ao re-executar um bloco, as variáveis locais reteriam os valores da execução anterior.

Se um novo binding de locação for feito a cada nova entrada no bloco, nenhuma suposição poderia ser feita.

Exemplo: program P declare I; begin P for I := 1 to 10 do block A; declare J; begin A if I = 1 then {I de P; J de A} J := 1 else J := J * I {assume-se que J retém valor de execução prévia} endif end A; end P

Desvantagem: memória para todos os blocos do programa deve ter reservada por todo o tempo de execução do programa.

Alternativa: (binding dinâmico) Fazer o binding de locação, bem como o de nome, em tempo de execução, a cada entrada de um bloco, desfazendo esses bindings quando da saída do bloco. (Alocação dinâmica de memória).

Extensão do binding: período de tempo, durante execução, em que o binding é válido.

Implementação: via registros de ativação que são registros contendo informações sobre uma unidade de execução, necessárias para restabelecer sua execução, depois de ela ter sido suspensa. Para efeito de binding de locação, o registro de ativação precisará conter apenas as locações para todos os objetos ligados localmente, mais um ponteiro para o registro de ativação do bloco que o contém.

Funcionamento: Conforme um bloco é ativado, seu registro é colocado no topo de uma pilha. No término de sua execução, seu registro é desempilhado.

Exemplo:

program P; declare I, J; begin P block A; declare I, K; begin A block B; declare I, L; begin B; ... end B; ... end A; block C; declare I, N; begin C ... end C; end P;

Uso da pilha para localizar objetos:

1. Procura no registro do topo pelo objeto ligado ao nome.

2. Se não encontrar, procure-o no registro apontado por ele; continue nesse processo até encontrá-lo na lista, ou a lista acabar.

Esta busca pode ser otimizada, uma vez que se sabe, em tempo de compilação, a estrutura da pilha e de cada registro de ativação.

Em C:

"source file" - outra unidade de execução

Uma declaração precedida pela palavra extern torna visível um objeto daquele nome, que tenha sido definido num source file diferente. Esses arquivos podem ser compilados separadamente e, portanto, servem como unidades de compilação.

extern int compare_strings();

33

34

extern char *read_string();

extern void copy_string();

8.1.1.5. Exercícios

1. Em quais situações bindings de locações são feitos em tempo de execução?

2. Que fatores deve-se considerar ao escolher entre um espaço de identificadores mais ou menos restritivo?

3. Algumas LPs permitem o binding de tipo a um objeto em tempo de execução. Discuta vantagens e desvantagens desta estratégia.

4. Quando uma LP oferece tipos reais em ponto fixo e em ponto flutuante, o que é importante considerar ao decidir o tipo de um dado objeto?

5. Que tipo de uso se faria de bindings de labels em tempo de execução? Que perigos isso oferece?

6. Considere o mecanismo de passagem de parâmetros do Pascal.

a) O binding feito entre os parâmetros atuais e os formais é estático ou dinâmico?

b) Especule como esse mecanismo funcionaria se fosse o oposto do que você respondeu acima.

8.1.2. Estruturas de controle

As estruturas de controle são alternativas ao modo seqüencial e podem ser: condicional, iterativa e desvio incondicional.

1) Condicional: determina bloco de execução baseado em testes. Existem 4 formas de estrutura condicional: condicional simples, condicional com 2 alternativas, condicional multi-alternativa e condicional não-determinística.

2) Estruturas Iterativas: iteração infinita, iteração pré-teste, iteração pós-teste, iteração in-teste, iteração com número fixo de passos e iteração não-determinística.

3) Desvio Incondicional.

8.1.2.1. Condicional Simples

Consiste de um teste único que determina se um bloco deve ou não ser executado.

if <expressão booleana> then <bloco de comandos>

Variação: delimitação do bloco de comandos:

ALGOL 60, Pascal: begin ... end

C: { ... }

ADA: endif (keyword)

35

MODULA 2: end (keyword)

8.1.2.2. Condicional Com 2 Alternativas

if <expressão boolena> then <bloco de comandos>

else <bloco de comandos>

Pascal: "dangling else" em condicionais aninhadas.

Exemplo: if x 0 then if x < 10 then x := x + 1 else {pertence a qual if?} x := x - 1;

Para inicialmente x = 12, se pertencer ao primeiro if, x = 12, se pertencer ao segundo, x = 11. A identação é irrelevante.

Possíveis soluções: if x 0 then begin if x < 10 then x := x + 1 else x := x - 1 end;

ou if x 0 then begin if x < 10 then x := x + 1 end else x := x - 1;

Regra implícita do Pascal: "else" fecha sempre o "if" mais próximo.

O uso de keywords evita ambigüidades:

ADA: if x 0 then if x < 10 then x := x + 1; else x := x - 1; endif;

36

endif; ou

if x 0 then if x < 10 then x := x + 1; endif; else x := x - 1; endif;

Outra fonte de erro no Pascal:

<comando>; else (neste caso ; é separador e não terminador como em C) que é interpretado como:

<comando>; <comando nulo> else (o que dá erro)

8.1.2.3. Condicional Multi-alternativas

Existem 2 formas possíveis:

1) Seqüência de expressões booleanas: a primeira expressão verdadeira determina o bloco de comandos a ser executado.

Exemplo:

Em ADA: if <cond1> then <comando1> elsif <cond2> then <comando2> ... elsif <condN> then <comandoN> [else <comandoN+1>] endif

Simulação em Pascal: if <cond>1 then begin <comando1> end else if <cond2> then begin <comando2> end else if ... ... (aninhamento) else begin <comandoN+1>

37

end; 2) Comando CASE: avalia uma expressão e executa bloco correspondente àquele valor. Obs: é

mais restrito que o caso 1.

Exemplo:

Em Pascal: case <expressão> of <val1>: <comando1> <val2>: <comando2> ... <valN>: <comandoN> end Onde <expressão> pode ser qualquer tipo discreto (int, char, tipo enumerado) e <val1> ...

<valN> são listas de valores.

Em C: switch (<expressão seletora>) { { case <expressão> : [<seq. de comandos>]} [default: <seq. de comandos>] } Onde:

<expressão seletora> é interpretada como um inteiro;

Cada expressão case pode especificar um único valor;

O bloco de comandos a ser executado começa na expressão case "matched"e vai até a última seqüência de comandos (mesmo as nulas) - ("break" interrompe essa série de execuções).

Exemplo: switch (i) { case 0: case 1: printf ("1"); case 2: printf ("2"); break; case 5: case 3: printf ("4"); default: printf ("5");

}

8.1.2.4. Condicional Não-Determinística

É uma extensão da condicional multi-alternativa proposta por Dijkstra (1975).

Forma geral proposta:

if <cond1> <seqüência de comandos 1>

38

when <cond2> <seqüência de comandos 2>

...

when <condN> <seqüência de comandos N>

fi

Todas as condições são avaliadas (e não até encontrar a primeira verdadeira). Quando mais de uma for satisfeita, a seqüência de comandos a ser executada é escolhida de forma não-determinística. Ou seja, não há uma regra para escolher uma delas - qualquer uma pode ser escolhida. Se nenhuma condição for satisfeita, um erro ocorre.

As condições são chamadas "guardas" ou "sentinelas".

ADA tem uma versão desse comando e a usa para implementar controle de concorrência.

8.1.2.5. Iteração Infinita

do forever

<seqüência de comandos>

end do

Simulação em Pascal: while true do begin <seqüência de comandos> end;

Em ADA: (comando LOOP) LOOP <seqüência de comandos> end LOOP;

8.1.2.6. Iteração Pré-Teste

Em Pascal: while <condição de continuação> do <corpo_iteração>

Em C: while (<expressão>) <corpo_iteração>

8.1.2.7. Iteração Pós-Teste

Em Pascal:

39

repeat <corpo_iteração> until <condição de terminação>

Em C: do <corpo_iteração> while (<expressão>)

Simulação em ADA: LOOP <corpo> exit when <condição>; end LOOP;

8.1.2.8. Iteração In-Teste

Teste no meio do corpo de iteração (caso bem justificado de "goto")

Em ADA: (através do comando exit [when <condição>]) LOOP <corpo_iteração1> exit when <condição> <corpo_iteração2> end LOOP;

Extensão: aninhamento LOOP <corpo_A> LOOP <corpo_B> exit when <condição> (sai do loop mais interno) <corpo_C> end LOOP; <corpo_D> end LOOP;

ou OUTER: LOOP (forma de rótulos exclusiva desse comando) <corpo_A> LOOP <corpo_B> <- exit OUTER when <condição> <corpo_C> end LOOP; <corpo_D> -> end LOOP OUTER;

Em C: (através dos comandos break e continue) do {

40

... do { (<-2) ... if (cond1) { break (1->) continue (2->) } } while (cond2); } (<-1) while (cond3);

8.1.2.9. Iteração com Número Fixo de Passos

O controle é feito por VC (Variável de Controle) e a forma mais geral é:

for <VC> := <V. inicial> to <V. final> step <incremento>

do <corpo>

ou

for (<V. in.>; <V. Contin.>; <expr. mod.>) <com>;

Onde todas as expressões são do mesmo tipo de VC.

Variações entre as linguagens:

• Tipos permitidos para VC:

• ADA, Pascal: integer, char, enumerados • FORTRAN: integer, real • C: integer

• Escopo de VC:

• ADA: equivale à declaração local • Pascal, C, FORTRAN: escopo da declaração

VC pode ser alterada no corpo da iteração?

Em geral, não.

Valor de VC depois do termino da iteração:

• ADA: não haverá binding de locação e valor • Pascal: indeterminado • C: valor final incrementado

Quando as expressões final e de incremento são avaliadas?

Em Pascal:

for i := 1 to n (I) do

n := n + 1;

41

Se (I) for reavaliada a cada passo, essa iteração seria infinita. Em geral, as 2 expressões (final e incremental) são avaliadas uma única vez, no início da iteração. Assim, a modificação de n, acima, não alteraria o número de passos inicial.

Formas de incremento:

• ADA, Pascal: "incremento de 1" (succ)

• C, FORTRAN: expressões

Decrementos:

• Pascal: for i := 6 downto 1 do ...

• ADA: for i in reverse 1..6 loop ... end loop

Desvios para o interior do loop

• ADA, FORTRAN: proíbem

• Pascal: permite

8.1.2.10. Iteração Não-Determinística

do

when <cond1> <seqüência de comandos1>

when <cond2> <seqüência de comandos2>

...

when <condN> <seqüência de comandosN>

od

Repete enquanto pelo menos uma condição for verdadeira. Se mais de uma é verdadeira, a escolha da seqüência é feita não-deterministicamente.

8.1.2.11. Desvio Incondicional

goto <label>

Módula 2 não tem esse comando.

Labels:

• Pascal: declarados

42

• C, FORTRAN, ADA: implícitos

• PL/I, SNOBOL, APL: variáveis (podem ser passados como parâmetros e permitem arrays da labels)

8.1.3. Tipos de Dados

Checagem de tipo

A checagem de tipo consiste na determinação do tipo de um certo dado objeto. Pode ocorrer em tempo de compilação, de execução ou pode não ocorrer. LPs com checagem de tipo em tempo de compilação são ditas fortemente tipadas (por exemplo, ADA). Pascal, por exemplo, não é fortemente tipada: a maior parte da checagem é feita em tempo de compilação, mas os subintervalos e os registros variantes não são checados. Quando não há possibilidade de haver checagem em tempo de compilação em determinadas circunstâncias, há duas alternativa: ou não se faz a checagem (Pascal), ou se faz em tempo de execução - o que pode ser muito caro em tempo (cada vez que o dado objeto é referido) e em espaço (um indicador de tipo deve ser armazenado como parte de cada valor de dado).

LPs dinamicamente tipadas, onde os bindings são feitos em tempo de execução, fazem checagem apenas em tempo de execução, enquanto que LPs estaticamente tipadas, com bindings em tempo de compilação, podem checar tipos ou em tempo de compilação ou em tempo de execução.

Exemplo da checagem em Pascal: type soft = record case test: boolean of true: (first: 1..20); false: (second: char); end; var x, y: soft; c: char; begin ... c := x.second; y.first := 2 * x.first; ... end.

Pascal não pode verificar totalmente os tipos por 2 razões:

1. Quando x.second é usado, não é possível checar, durante a compilação, se a segunda parte variante de x está ativada. (se x.teste = true, então x.second tem "lixo")

2. Na atribuição a y.first, haverá violação se x.first 10, o que não é possível verificar em tempo de compilação.

Conversão de tipos

A conversão de tipos pode ser implícita (coerção de tipos) ou explicita. Por exemplo, há conversão implícita entre um "integer" e um "real" e explicita quando se usa float(), round, trunc.

Quando dois tipos são considerados iguais?

43

Sejam os tipos e variáveis: type T1: 0..10; T2: 0..10; var A: T1; B, C: T1; D: T2;

São legais as seguintes atribuições? A := B; B := C; A := D;

Existem 3 perspectivas para se estabelecer a igualdade entre tipos:

1. Equivalência de domínios: dois dados objetos são equivalentes se tiverem associados a seus tipos um mesmo domínio de valores possíveis (equivalência estrutural). Neste caso A := B; A := D; e B := C são legais (A, B, C, D são tipos equivalentes). É o que ocorre em ADA.

2. Equivalência de nome: dois dados objetos são de tipos equivalentes se seus tipos tiverem o mesmo nome. Neste caso A, B e C são variáveis de tipos equivalentes, mas D não é. É o que ocorre em Pascal.

3. Equivalência de declaração: dois dados objetos são de tipos equivalentes se eles são ligados ao tipo numa mesma declaração. Neste caso só B e C são de tipos equivalentes.

E os tipos anônimos?

Por exemplo (ADA, Pascal):

var E, F: 0..10;

G: 0..10;

Neste caso não existe equivalência de nome.

Subtipos e Tipos Derivados

Por exemplo:

var A: integer;

B: 0..100;

A := B é válido? e B := A

O subtipo inclui o conjunto de operadores do tipo principal. Nestes casos a checagem é realizada em tempo de execução.

Forçando incompatibilidade em LP com equivalência de domínio

(ADA):

type tempo is new float;

44

comprimento is new float;

duração: tempo;

distância: comprimento;

Neste caso o domínio de "duração" é igual ao domínio de "distância" mas a operação "duração + distância" não é permitida.

Tipos de dados Escalares

São aqueles dos dados objetos atômicos, que não podem ser desmembrados em componentes. Podem ser Built-in types (já presentes na LP) ou User-Defined types (definidos pelo usuário).

Propriedades a considerar: 1. Parâmetros do tipo (por exemplo, precisão) 2. Formato da declaração 3. Domínio 4. Operações (operadores e funções) 5. Atributos 6. Conversão 7. Constantes pré-definidas 8. Implementação

As LPs em geral apresentam os seguintes tipos básicos: Tipo Numérico – Inteiro, Tipo Numérico – Real, Tipo Boolean, Tipo Ponteiro, Tipos Enumerados e Tipos Compostos.

8.1.3.1. Tipo Numérico - Inteiro

Algumas LPs suportam um único tipo inteiro ( integer) cujo domínio é o intervalo dos inteiros que podem ser representados na máquina. A desvantagem desta implementação é que tem o domínio dependente de implementação.

Outras LPs permitem a especificação de subtipos de inteiros, limitando o domínio a um intervalo do domínio de inteiros da máquina.

Exemplos:

• Módula 2: "integer" e "cardinal" (não-negativos) • C: "short int", "int", "long int", "unsigned" • Pascal: type Positivos = 1..maxint • ADA: subtype T is Integer range 0..maxint (o tipo é derivado do tipo das constantes

do intervalo) A checagem de tipos para os subtipos pode ser de duas maneiras:

• ignorar a checagem, já que há necessidade de checagem em tempo de execução - o que custa tempo.

• checar, em tempo de execução, toda vez que um novo binding de valor é feito A escolha entre essas opções pode ser feita em tempo de compilação.

Implementação

45

Geralmente a linguagem segue a representação de inteiros da máquina em que a LP é implementada. (em geral binário com complemento de 2)

Operações

São em geral implementadas diretamente em linguagem de máquina (LM) ou podem ser programadas em LM usando algoritmos aritméticos padrões.

8.1.3.2. Tipo Numérico - Real

Possíveis parâmetros:

• número de dígitos significativos • fator de escala designando o número de dígitos à direita do ponto decimal

Exemplo:

Números reais com 10 dígitos significativos e fator de escala de 2 dígitos representariam todos os números de -99999999.99 até 99999999.99 com intervalo de 0.01 de distância (representação em ponto fixo).

A maioria das LPs utiliza representação com ponto flutuante (mantissa e expoente)

ADA utiliza tanto ponto fixo como ponto flutuante.

8.1.3.3. Tipo Boolean

Domínio = {Falso, Verdadeiro}

Em C representado como 0 e 1 (boolean é um subtipo de integer)

Operadores: AND, OR, NOT

Considere: (I <> 0) AND (K/I > 6)

Se I <> 0 for falso, então K/I não pode ser executado, desde que I é zero.

Mas, se a primeira condição é falsa, então não há necessidade de avaliar a segunda, já que a conjunção será falsa. Então uma implementação mais esperta prevê:

A AND B = if A then B else FALSE

e

A OR B = if A then TRUE else B

ADA possui AND THEN e OR ELSE - opcional para programador - além de AND e OR.

Constantes FALSE e TRUE.

8.1.3.4. Tipo Ponteiro

O valor de um ponteiro é o endereço de um outro dado objeto. Neste caso existem dois dados objetos envolvidos: o apontador e o apontado.

Bindings para ponteiros

Neste caso, os bindings de tipo e nome ocorrem em tempo de compilação, o binding de locação ocorre em tempo de loading e o de valor, em tempo de execução.

O binding estático de tipo requer a declaração do tipo do objeto apontado (ADA, PASCAL, C), já no binding dinâmico de tipo o tipo do objeto apontado é derivado do valor a ele atribuído em tempo de execução.

Exemplo:

• PASCAL: var P: ^ tipo; • ADA: P: access ^ tipo; • C: int * P;

O binding de valor a um ponteiro é feito de duas formas:

• criação

• Pascal - new (P); • ADA - P := new tipo; ou P := new integer'(0); - cria e aponta para o inteiro 0 • C - malloc (P);

• atribuição

• Pascal - P := Q; - onde var P, Q: ^tipo;

• C (operadores de ponteiros) -

46

int *P;

int X;

*P = X;

P = &X;

Desalocação de objetos apontados em tempo de execução:

• Pascal - dispose (P);

• ADA, C - free (P);

Atenção (desalocação implícita x explícita) : uma vez desalocado o objeto apontado, é desfeito o binding de valor do objeto ponteiro - o que inviabiliza o uso do elemento apontado. Esses ponteiros são ditos estar "suspensos". (Mas eles podem ter reestabelecido o binding de valor através de uma atribuição). O objeto apontado desalocado é dito garbage, impossível de ser alcançado pelo programa. (Garbage Collection - Alocação dinâmica)

47

"Deferenciar" um ponteiro é o processo de obter o valor do objeto apontado, através da referência do nome do ponteiro.

Exemplo:

• Pascal - P^

• ADA - P.ALL

• C - *P

Constantes utilizadas:

• Pascal - NIL

• ADA, C – NULL

8.1.3.5. Tipos Enumerados (domínio criado pelo usuário)

O usuário cria e dá nome a uma lista finita de valores. Em Pascal por exemplo:

type cor = (verde, azul, branco);

var c: cor;

Operadores: atribuição e comparação (ordem em que aparecem na lista - verde < azul < branco). Em Pascal existe também succ( ) e pred( ).

Implementação: mapeamento nos inteiros não negativos 0, 1, 2, ... .

48

49

Dificuldades:

1) Mesmo identificador em tipos distintos

Exemplo:

type fruta = (maçã, laranja, pêra); cor = (branco, azul, laranja);

var F: fruta; C: cor;

Pascal proíbe, ADA permite e trata como:

Exemplo:

f := laranja - fruta (pelo contexto - F é do tipo fruta)

Em contextos ambíguos, permite o uso da forma F := fruta'laranja.

2) Input/Output

Pascal não permite. Outras LPs provêm conversão automática entre strings e representação interna.

Ex: r. u. string

0 - maçã - 'maçã'

1 - laranja - 'laranja'

2 - pêra - 'pêra'

8.1.3.6. Tipos de Dados Compostos

Existem várias formas de composição, correspondendo aos diversos tipos de dados compostos:

• Mapeamento: f: T1 -> T2 (arrays)

• Produto Cartesiano: T = T1 x T2 x ... x Tn (records)

• União discriminante: T = T1 U T2 U ... U Tn (records variantes)

• Seqüência: T = {(t1, t2, ..., tn)} (strings, files)

• Conjunto Potência: T = 2T1 (sets)

8.1.3.6.1. Array

<def. array> ::= array [<tipo domínio>] of <tipo base>

onde <tipo domínio> é finito e <tipo base> é qualquer.

Em Pascal, FORTRAN e C o binding de <tipo domínio> é feito em tempo de compilação. Isso determina a impossibilidade de usar parâmetros, do tipo array, com domínios gerais.

50

Exemplo: type TipoA1 = array [1..10] of integer; TipoA2 = array [1..20] of integer; var A1: TipoA1; A2: TipoA2; procedure media1(A: TipoA1, var media: integer); procedure media2(A: TipoA2, var media: integer); ... media1(A1, m); media2(A2, m);

Em ADA a definição é irrestrita.

Exemplo: type vector is array (integer range <>) of float; var V: vector (INF..SUP); {declaração local a um bloco}

APL e SNOBOL permitem alteração do domínio.

Seleção:

a[i]

a(i)

slice: a (3..N) - ADA

Construção:

ADA: (1.0, 5.0, 3.0, 6.0)

Extensão: (1 -> 7.0, 5..12 -> 1.0, others -> 0.0)

C: int name[10] = {6, 4, 8, 9, 2, 7, 17, 21,16, 8}

Atribuição:

ADA: V := (1.0, 3.0, 5.0)

V1 := V2 (se tipos_bases e cardinalidades dos tipos_domínio forem iguais)

V1 is array (1..10) of integer;

V2 is array (11..20) of integer;

Operadores de composição:

Se A = (1, 2, 3, 4) e B = (5, 9, 7, 1)

A + B = (6, 11, 10, 5)

ADA: Sobre arrays booleanos: and, or, not, xor.

Operadores de agregação:

APL: + (14, 12, 1) = 27

Atributos derivados da estrutura:

C: sizeof()

ADA: first, last, range, length

Exemplo:

type TipoA is array (integer range <>) of character;

A: TipoA (4..12);

A'first é 4

A'last é 12

A'range é 4..12

A'length é 9

Comparação: (ordem lexicográfica)

ADA, Pascal.

Implementação:

Exemplo:

Endereço I = Base_loc + (I - Dom_1o) * Base_tam

Extensão para arrays multidimensionais - armazenamento por linha ou coluna.

51

8.1.3.6.2. Records ou Structures

Pascal: type T = record C1: integer; C2: real; end; var R: T;

C: struct T { int C1; float C2; } struct T R = {10, 5.7}

Obs: Pode haver comparação de registro de igual estrutura (igualdade e desigualdade).

8.1.3.6.3. Records Variantes

Modelo: união discriminante (presença de um componente que determina qual dos conjuntos de tipos é ativo num dado momento).

ADA: type T is record case D: 1..4 is when 1 -> C1: T1; when 2 -> C2: T2; when 3 -> C3: T3; when 4 -> C4: T4; end case; end record;

Pascal: type V = record a: integer; case b: boolean of true: (C: integer); false: (D: integer; E: real); end;

Em C : unions - indiscriminadas

52

Representação:

Implementação: A memória reservada para parte variante corresponde ao maior espaço necessário para armazenar qualquer alternativa.

Agregados Aninhados

Ex. em ADA: X: record A: string; B: array (1..3) of character; C: integer; case D 1..2 is when 1 -> E: integer; F: string; when 2 -> G: real; H: integer; end case; end record;

53

8.1.3.6.4. Strings

Existem 2 tipos de modelagem:

1) Como arrays de caracteres (Pascal, C, ADA), possuindo tamanho (máximo) fixo, e inexistência de operadores próprios (funções).

2) Como seqüências "dinâmicas" (SNOBOL, BASIC), possuindo tamanho ilimitado e operadores próprios (concatenação, substring). Neste caso o binding é feito em tempo de execução.

Implementação:

54

8.1.3.6.5. Arquivos (Files)

Seqüências de componentes de um mesmo tipo, armazenadas em memória secundária.

Tipo x Facilidade de E/S

Pascal: file of <tipo_componente>

S.O. : binding de locação (assign)

Operações: reset, rewrite, get, put, seleção: <nome_arq>^, eof ().

8.1.3.6.6. Conjuntos (Sets)

Pascal: set of <tipo_base>

type S = set o 1..4;

Valores possíveis: [], [1], [2], [3], [4], [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4], [1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4], [1, 2, 3, 4].

| tipo_base | = C -> | tipo_conjunto | = 2C

55

Implementação: array de boolean

Ex: var X: S

X := [1, 3]

8.1.3.6.7. Exercícios

1. Considere as declarações: type S: 1..100; T: 1..100; var A, B: T; C, D: S; E, F: 1..100; G, H: T;

Quais variáveis são de tipos equivalentes se a linguagem usa:

a) Equivalência de Domínio

b) Equivalência de Nome

c) Equivalência de Declaração

2. Operadores que aparecem em mais de um tipo são ditos overloaded. Identifique os operadores overloaded no Pascal e em C.

3. Considere as definições de blocos a seguir. Para cada bloco, determine os bindings de cada identificador. Diga se é local ou global.

program P; block B1; declare A, B, C; block B2; declare C, D; block B3; declare B1, D2, F3;

56

57

begin ... end B3; begin ... end B2; block B4; declare B, C, D; begin ... end B4; begin ... end B1; end P;

4. Considere o programa Pascal: program quarta (input, output); type t = integer; var a, b, c: integer; procedure p1; var a: real; begin a := 1; b := 1; c := 1; end; procedure p2; var b: real; t: boolean; procedure p3; var a, c: real; t: real; begin a := 3; b := 3; c := 3; p1; end; begin a := 2; b := 2; c := 2; p3; end; begin a := 0; b := 0; c := 0; p2; end.

Quando a execução de p1 começa, a estrutura de chamada é da forma:

programa principal - p2 - p3 - p1

a) Usando a regra estática de escopo de Pascal, diga quais variáveis são válidas em cada rotina. Use a notação <var> de <proc> para denotar <var> é acessível e é definida em <proc>

b) Faça o mesmo, agora assumindo regras de escopo dinâmico.

58

5. Considere o fragmento Pascal e preencha a tabela com indicações de Legal ou Ilegal, com respeito a equivalência de tipo. Indique o tipo de equivalência entre os 2 lados da atribuição.

type t1 = 1..20; t2 = 5..10; t3 = record f1: boolean; f2: integer; end; var a, b: t1; c: integer; d: t2; e: 1..5; f, g: t3; h, i: record f1: boolean; f2: integer; end;

Estrutural Nome Declaração

a := b

c := a

d := e

f.f2 := c

f := g

h := g

i := h

i := j

6. Qual o valor de "a", usando: (1) regras estáticas de escopo, e (2) regras dinâmicas de escopo. program seis (input, output); var a, b: integer; procedure p1; begin b := 4; end; procedure p2; var b: integer; begin

a := 50; p1; end; begin b := 40; p2; a := b; end.

8.1.4. Procedimentos e Funções

Abstrações de programas -> expressões (funções) ou comandos

Analogia:

Tipo de Dado <-> Tipo de Procedimento

A definição é feita em tempo de compilação.

Invocação (chamada):

Novo binding do objeto (procedimento) a uma ativação do procedimento (em tempo de execução).

Analogia:

Def. Tipos de Dados x Def. de Procedimentos

59

Def. Variáveis (a cada entrada do bloco, criação de um objeto) x Ativação de Procedimentos (a cada chamada, criação de objeto de ativação)

Como resultado de chamada -> criação de um registro de ativação (R.A.)

A forma geral de um R.A. consiste de três partes:

• ambiente local (objetos definidos localmente no procedimento) • ponteiro para ambiente global (ponteiro para registro de ativação do qual a ativação

atual herda objetos e seus bindings - objetos globais) • ambiente de parâmetros (informação sobre os objetos passados para e a partir do

procedimento) Pilha de R.A.

Quando um procedimento é chamado, seu R.A. é empilhado numa pilha de R.A., durante o tempo de execução. Quando ele termina, seu R.A. é desempilhado, fazendo com que a unidade que o chamou passe a ter seu R.A. no topo da pilha e portanto, a unidade ativa atual. Assim, a pilha de R.A. representa todas as unidades de programa que estão em "processo de execução" (apenas a do topo está sendo executada no momento).

Exemplo:

60

Chamadas recursivas geram R.A. distintos.

8.1.4.1. O ambiente local

O ambiente local inclui:

a) os objetos declarados localmente no procedimento

b) um ponteiro para a próxima instrução a ser executada no procedimento (endereço de retorno) - cada ativação possui um registro de ativação, assim, há múltiplos endereços de retorno

c) memória temporária para reter dados durante avaliação de expressões. (ex: chamada de função durante avaliação de expressão) -> dependente de implementação.

Estendendo o exemplo anterior com o ambiente local (instante p -> s -> r -> r -> q da pilha):

61

62

8.1.4.2. O ambiente global

Permite acesso a objetos definidos fora do procedimento. O ambiente onde esses objetos são encontrados é representado no R.A. por um ponteiro para o R.A. cujos ambientes local e global irão determinar o ambiente global do R.A. atual. Ou seja, quando um objeto não é encontrado localmente (na porção do ambiente local do R.A.), uma busca é efetuada a partir do ponteiro do ambiente global deste R.A..

Possíveis definições de ambiente global:

• Escopo Estático

• Escopo Dinâmico

Uma terceira alternativa é a utilização unicamente de ambiente local. Com isso elimina-se riscos de efeitos colaterais (alteração de um ambiente sem mudança especificada no código).

FORTRAN: todas as variáveis são locais, exceto as definidas com o comando COMMON.

Usual: toda as variáveis são globais, exceto as definidas localmente.

C: automatic, external, static.

8.1.4.2.1. Escopo Estático

O ambiente global é herdado da unidade em que o procedimento foi definido. Portanto um nome usado no procedimento, mas não "ligado" (definido) nele, herda o binding que este nome tinha na unidade de programa que contém (imediatamente) o procedimento. (= definição de escopo estático de blocos).

A definição do ambiente global é feita em tempo de compilação, já que depende unicamente da posição da definição do procedimento. No entanto, ele é especificado em tempo de execução no R.A. porque ele precisa se referir a um R.A. específico da unidade que o contém.

Exemplo:

8.1.4.2.2. Escopo Dinâmico

O ambiente global é determinado pelos ambientes local e global da unidade que chamou o procedimento. Sua determinação é feita em tempo de execução.

Vantagem: eliminação do ponteiro para ambiente global (implícito na pilha).

Desvantagem: ilegibilidade e fonte de erros.

Exemplo:

63

8.1.4.3. O ambiente de parâmetros

Parâmetros são o meio de comunicação entre unidades de programa. Existem 3 categorias de parâmetros:

• Parâmetro de Entrada (IN): informação da unidade que chamou para o procedimento.

• Parâmetro de Saída (OUT): informação do procedimento para a unidade que o chamou.

• Parâmetro de Entrada/Saída (IN/OUT): informação em ambos os sentidos.

Parâmetro atual é aquele que aparece na chamada do procedimento (valor - IN, endereço - OUT ou IN/OUT) e parâmetro formal é aquele que aparece na definição. A associação dos parâmetros pode ocorrer em tempo de compilação ou em tempo de execução.

Em C todos os parâmetros são IN e implementados por cópia (referência pode ser feita via ponteiros).

Em Pascal: IN/OUT - referência (var), IN - cópia (default)

Métodos de Associação de Parâmetros:

• posicional: posições nas listas

• por nome: nome do parâmetro formal é associado ao atual na chamada

Exemplo:

ADA utiliza os dois métodos e ainda um misto:

procedure TEST (A: in TipoA; B: in out TipoB; C: out TipoC);

64

65

Associação posicional: TEST (x, y, z);

Associação por nome: TEST (A->x, C->z, B->y);

Associação mista: TEST (x, C->z, B->y);

Existe ainda a associação por default: (para parâmetro IN) na ausência de parâmetro atual, assume-se valor default especificado para parâmetros formais.

8.1.4.3.1. Parâmetros de Entrada (IN)

Podem ser implementados de duas maneiras:

• como Referência: o endereço do parâmetro atual torna-se o endereço do parâmetro formal. Modificações no parâmetro formal alteram o atual. Neste caso deve-se haver uma verificação de não-alteração: não pode aparecer do lado esquerdo de atribuição e como parâmetro OUT ou IN/OUT em outro procedimento. Neste caso o parâmetro deve ser tratado como constante no procedimento.

• como Cópia (valor): o parâmetro formal é tratado como variável local inicializada com o valor do parâmetro atual. Modificações no parâmetro formal não afetam o parâmetro atual. Este método é menos eficiente em tempo (cópia) e espaço (variáveis locais), porém é mais flexível, permitindo a modificação dos parâmetros formais e um menor número de variáveis locais.

8.1.4.3.2. Parâmetros de Entrada e Saída (IN/OUT)

Podem ser implementados de duas maneiras:

• por Referência: não há restrições ao uso do parâmetro formal no procedimento. Os parâmetros formal e atual compartilham o mesmo endereço.

• por Cópia (valor): uma variável local é criada para o parâmetro formal e inicializada com o valor do parâmetro atual. Ao final do procedimento o valor final dessa variável é copiado no endereço do parâmetro atual.

Nos dois casos o efeito final é o mesmo, com locações diferentes.

Problema: "Aliasing" - referência a uma mesma locação por diferentes nomes.

Exemplo em Pascal: program main; var A: integer; procedure test(var X, Y: integer); {var -> IN/OUT} begin X := A + Y; writeln(A, X, Y); end; begin A := 1;

66

test(A, A); writeln(A); end.

Se a implementação dos parâmetros for por referência o resultado deste programa será

2 2 2

2

porém ser a implementação for por valor, o resultado será

1 2 1

? - depende da ordem da cópia de X, Y em A

8.1.4.3.3. Parâmetros de Saída (OUT)

(Pouco usado)

Podem ser implementados de duas maneiras:

Referência: compartilham endereço. Deve haver verificação para que o valor do parâmetro atual não seja usado no procedimento; apenas modificações ao parâmetro formal são permitidas.

Cópia (valor): parâmetro formal é uma variável local com valor inicial indefinido. No final do procedimento, o valor do parâmetro formal é passado ao parâmetro atual.

8.1.4.3.4. Procedimentos como parâmetros

Ao especificar o parâmetro (procedimento) formal, é necessário que os tipos de todos os seus parâmetros também sejam especificados a fim de que a checagem de tipos possa ser feita quando o procedimento (formal) for chamado.

Exemplo em Pascal: program main; var A: real; procedure TESTPOS(X: real; procedure ERROR(MSG: string)); begin if x <= 0 then error('X negativo em TESTPOS'); end procedure E1(M: string); begin writeln('E1 Erro:', M); end; procedure E2(M: string); begin writeln('E2 Erro:', M); end; begin

67

readln(A); TESTPOS(A, E1); TESTPOS(A, E2); end.

Informações necessárias para o procedimento-parâmetro sobre o procedimento que o tem como parâmetro:

• endereço para o código executável

• ponteiro para o R.A. (registro de ativação) do ambiente global do procedimento que o contém

Estas informações são necessárias para a criação do R.A. do procedimento-parâmetro.

Em C funções podem ser parâmetros de outras funções através de "ponteiros para funções".

Exemplo em C: Função que soma N termos resultantes da aplicação de uma função arbitrária aos primeiros N inteiros.

sum (N, F) int N; int (*F)(); { int i, s; s = 0; for (i = 1; i <= N; i++) s = s + (*F)(i); return (s); } square (i) int i; { return(i*i); } main () /* soma dos quadrados dos primeiros 10 inteiros */ { int square(); printf("%d\n",sum(10, square)); }

8.1.4.3.5. Passagem de Parâmetro "Por Nome"

(ALGOL 60)

Na chamada do procedimento ocorre um binding do nome do parâmetro atual com o parâmetro formal. Ou seja, em run-time, há uma "substituição textual" do nome do parâmetro atual pelo parâmetro formal dentro do procedimento. A locação de memória do parâmetro atual permanece a mesma durante a execução do procedimento.

Exemplo: procedure swap(a, b: integer);

68

var temp: integer; begin temp := a; a := b; b := temp; end; program main; var i, j: integer m: array[1 .. 100] of integer; begin ... swap(i, j); ... end.

Se a passagem for por nome: temp := j; j := i; i := temp;

Efeito: igual a parâmetro IN/OUT por referência.

Problemas: swap(i, m[i]);

Após a substituição textual: temp := i; i := m[i]; m[i] := temp; (neste caso o "i" está modificado)

Compare com passagem por referência

Quando o nome do parâmetro atual coincide com o de uma variável local: program main; var i, temp: integer; begin swap(i, temp); end.

Então teremos: temp := i; i := temp; temp := temp;

Dois objetos estão ligados ao nome temp no procedimento (um local ao procedimento, outro local ao bloco que o chamou).

Método para diferenciar do ALGOL 60: nomes de parâmetros ligados a objetos da unidade que chamou.

No exemplo:

69

tempswap := i; i := tempmain; tempmain := tempswap;

8.1.4.4. Funções ou Value-Returning Procedures (VRPs)

• são abstrações de expressões

• retornam um único valor - do tipo da função

Existem dois métodos para implementar o valor de retorno:

1) (Pascal): cria-se uma pseudo-variável no ambiente local, ligada ao nome da função.

Diferenças com variável local: não é declarada e só pode ser modificada, mas não acessada, dentro da função. O valor dessa variável, por ocasião do término da execução, é o valor de retorno da função.

Custo: objeto para armazenar o valor de retorno.

2) (ADA, C): uso do comando RETURN seguido de uma expressão. Quando este comando é executado, a expressão é avaliada, a função terminada, e o valor da expressão é o valor de retorno da função.

Modos de passagem de parâmetros: idênticos aos de procedimento, porém os "efeitos colaterais" do uso de parâmetros OUT e IN/OUT, numa expressão, não são desejáveis.

8.1.4.5. Overloading de Procedimentos

O overloading de procedimentos permite que dois ou mais procedimentos tenham o mesmo nome se eles puderem ser distinguidos pelo número ou tipo de seus parâmetros, ou pelo tipo do valor de retorno no caso de funções.

Vantagem: procedimentos que fazem a mesma operação sobre diferentes tipos de parâmetros podem possuir o mesmo nome.

Exemplo em ADA: procedure main is R: float := 0.0; I: integer := 0; function F(X: float) return integer is begin return 1; end; function F(X: integer) return integer is begin return 2; end; function F(X: float; Y: integer) return integer is begin return 3;

end; function F(X: integer; Y: float) return integer is begin return 4; end; function F(X: integer) return float is begin return 5.0; end; begin -- main put(F(R)); -- imprime 1 put(F(I)); -- imprime 2 put(F(R, I)); -- imprime 3 put(F(I, R)); -- imprime 4 R := F(I); -- put(F(integer(R))); -- imprime 5.0 end main;

8.1.4.6. Co-rotinas

Enquanto os procedimentos são executados do começo ao fim, uma co-rotina executa a partir do ponto em que foi suspensa até a próxima instrução que suspenda sua execução.

ACL - Marlin (1980): linguagem derivada de Pascal, que adiciona (1) co-rotinas e (2) regras explícitas de escopo.

Exemplo: program Contador(input, output); coroutine Count; var i, cont: integer; initbegin {execução apenas na criação} cont := 0; initend;

70

71

begin {executado a qualquer chamada} for i := 1 to 5 do begin cont := cont + 1; writeln(cont:1); end; return; {suspende a execução} for i := 1 to 5 do begin cont := cont + 1; writeln(cont:1); end; return; end; var C: instance of Count; begin C := create Count; {cria RA para C e liga ao corpo Count} call C; writeln('Metade do caminho'); call C; delete C; {elimina RA} end.

8.1.4.7. Exercícios

1. Dê algumas vantagens do escopo dinâmico sobre o estático na definição do ambiente global de um procedimento.

2. Dê as vantagens e desvantagens relativas de se acabar com a idéia de ambiente global em procedimentos.

3. Qual o resultado de se ter apenas parâmetros IN/OUT? Qual a importância, nesse caso, do modo de passagem de parâmetros (referência ou cópia)?

4. Discuta algumas das dificuldades de se permitir passagem de parâmetro do tipo "label".

5. Algumas LPs provém entradas múltiplas (em diferentes pontos) num mesmo procedimento, que são chamadas por diferentes parâmetros associados a elas. Quais são os benefícios e perigos disso?

8.1.5. Tipo Abstrato de Dados (TAD)

8.1.5.1. Definição

Um TAD é uma coleção de estruturas de dados mais operações, abstraídos num único tipo de dado.

Ex: TAD bst - binary search tree. Consiste de objetos que pertencem à classe das Árvores de Busca Binária, juntamente com um conjunto de operações que poderiam ser aplicadas a esse tipo de árvore tais como:

72

initialize_tree(tree: out bst);

Inicializa a árvore como vazia.

empty(tree: in bst): boolean;

Retorna true se tree vazia; false, caso contrário.

root(tree: in bst): item_type;

Retorna o valor do item da raiz de tree. Ocorre uma exceção se vazia.

left_subtree(tree: in bst): bst;

Retorna a árvore de busca binária que é a sub-árvore esquerda. Exceção se vazia.

right_subtree(tree: in bst): bst;

Retorna a árvore de busca binária que é a sub-árvore direita. Exceção se vazia.

insert_in_tree(item: in item_type, tree: in out bst);

Insere item em tree na sua posição correta.

Este TAD poderia então ser usado para definir e manipular ABB (árvore binária de busca) sem conhecimento da estrutura de dados usada para representar ABB e dos algoritmos usados para implementar as operações.

Por exemplo, um procedimento para contar os nós de uma ABB poderia ser: procedure count_nodes(tree: in bst): integer; begin if empty(tree) then return 0 else return count_nodes(left_subtree(tree)) + count_nodes(right_subtree(tree)) + 1; endif; end;

O ponto importante é que os objetos de um TAD podem ser usados sem qualquer conhecimento de sua implementação. Todas as operações sobre os elementos do TAD devem fazer parte do TAD; nenhuma outra operação sobre eles é permitida.

Vantagens:

• Independência do uso do TAD de sua implementação, o que permite modificação na implementação sem afetar as unidades onde o TAD é definido.

• Manutenção da integridade do TAD, pela restrição do acesso às operações oferecidas. Por exemplo, se a ABB foi implementada usando ponteiros, não é possível uma unidade, usando um objeto bst, destruir sua estrutura, já que não terá acesso direto a ela.

73

8.1.5.2. Implementação

Há duas abordagens para a definição de TAD:

1ª) Extensão da definição de tipo para incluir a definição de operações.

Exemplo: ABB - bst type bst is export: initialize_tree, insert_in_tree, empty, root, left_subtree, right_subtree; {define operações visíveis a outras unidades que usam bst} end export; structure: access node; {define o tipo de dado propriamente dito} end structure; local: type node is record {define objetos não exportáveis e implementação não visível às demais unidades} root_item: item_type; left: bst; right: bst; end node; procedure initialize_tree(tree: in out bst) is tree := NIL; end initialize_tree; procedure empty(tree: in bst) return boolean is return (tree = NIL); end empty; procedure left_subtree(tree: in bst) return bst is begin if tree = NIL then raise bst_error; else return (tree.left); end left_subtree; procedure right_subtree(tree: in bst) return bst is begin if tree = NIL then raise bst_error; else return (tree.right); end right_subtree; procedure insert_in_tree(item: in item_type, tree: in out bst) is begin if tree = NIL then tree := new node(item, NIL, NIL) else if item < tree.root_item then insert_in_tree(item, tree.left); else insert_in_tree(item, tree.right); end insert_in_tree; procedure root(tree: in bst) return item_type is begin if tree = NIL then raise bst_error; else

74

return tree.root_item; end root; end local; 2ª) Mais geral; usada para definir outras entidades além de TAD. Consiste de uma coleção de

definições de objetos divididos em duas classes: aqueles que serão visíveis a unidades externas, e aqueles que serão invisíveis. Quando este formato é usado para definir TAD, o próprio TAD é incluído como um dos objetos definidos que será exportado com a coleção de definições visíveis - ao contrário da seção separada em que é definido na primeira abordagem. Veja o mesmo exemplo anterior utilizando essa abordagem:

collection binary_search_tree is export: type bst is private initialize_tree(tree: in out bst); insert_in_tree(item: in item_type, tree: in out bst); empty(tree: in bst) return boolean; root(tree: in bst) return item_type; left_subtree(tree: in bst) return bst; right_subtree(tree: in bst) return bst; local: type bst is access node; type node is record root_item: item_type; left: bst; right: bst; end node; procedure initialize_tree(tree: in out bst) is tree := NIL; end initialize_tree; procedure empty(tree: in bst) return boolean is return (tree = NIL); end empty; procedure left_subtree(tree: in bst) return bst is begin if tree = NIL then raise bst_error; else return (tree.left); end left_subtree; procedure right_subtree(tree: in bst) return bst is begin if tree = NIL then raise bst_error; else return (tree.right); end right_subtree; procedure insert_in_tree(item: in item_type, tree: in out bst) is begin if tree = NIL then tree := new node(item, NIL, NIL) else if item < tree.root_item then insert_in_tree(item, tree.left); else insert_in_tree(item, tree.right); end insert_in_tree;

75

procedure root(tree: in bst) return item_type is begin if tree = NIL then raise bst_error; else return tree.root_item; end root; end local; Esta abordagem serve para se definir múltiplos tipos dentro de uma única coleção ou, se nenhum

tipo estiver na seção export, simplesmente exportar uma coleção de procedimentos.

Esta definição tem duas partes principais, a informação que é visível a uma unidade externa, e a informação que fica escondida.

A parte visível pode conter: variáveis, constantes, headers de procedimentos, definições de tipo, ou nomes de tipos.

A parte invisível pode conter: definições de tipos cujos nomes aparecem na parte visível, corpos de procedimentos, e definições completas de objetos locais, tais como variáveis, tipos e procedimentos.

Algumas LPs permitem a inclusão de um bloco de comandos, na seção invisível, para ser executado para inicializar uma variável definida daquele tipo (por exemplo, substituindo initialize_tree). E também um bloco de comandos para ser executado sempre que a unidade, em que aparece uma variável daquele TAD, chega a seu final (por exemplo, para liberar o espaço ocupado por uma bst).

Outra alternativa de algumas LPs é implementar variáveis locais como own variables - isto é, essas variáveis retém seus valores de uma ativação de procedimentos TAD para outra.

8.1.5.3. Parametrização de Tipos de Dados

type ATYPE is array(integer range <>) of character;

A: ATYPE(4..12); {4..12 - parâmetro}

Vamos estender essa idéia para TAD:

Por exemplo, podemos parametrizar o tipo item_type do TAD bst anterior. Isto permitiria o mesmo TAD representar ABB onde os nós fossem de qualquer tipo, dentre os especificados pelos parâmetros possíveis.

Para implementar isto, adiciona-se uma seção para especificar parâmetros, nas definições de TAD:

/ abordagem 1 / type bst is parameters: type item_type; end parameters; export: .... / abordagem 2 /

76

collection binary_search_module is parameters: type item_type; end parameters; export: .... O binding entre parâmetros atuais e formais se dá de forma distinta nas duas abordagens.

Na 1ª, usa-se nomes de tipos com parâmetros, na declaração de variáveis. Por exemplo: A: bst(real); B: bst(string); C: bst(employee_record);

A 2ª forma requer instanciações da coleção, para cada conjunto de parâmetros aplicado. Isto significa que a coleção parametrizada é uma shell: quando os parâmetros são preenchidos, coleções são criadas, ou instanciadas. Por exemplo, coleções seriam instanciadas com os parâmetros acima, da forma:

collection bst_real is bst(real); collection bst_string is bst(string); collection bst_employee_record is bst(employee_record);

E as declarações de variáveis seriam da forma: A: bst_real.bst; B: bst_string.bst; C: bst_employee_record.bst;

Neste caso, como 3 coleções foram criadas, na definição de variáveis é necessário pré-fixar o tipo com o nome da coleção apropriada. Essa regra se aplica não apenas a tipos, mas a todos os objetos visíveis definidos nas coleções instanciadas.

Outros objetos podem ser definidos como parâmetros em TAD. Por exemplo, o operador < no procedimento insert_in_tree é válido se item_type for integer, real ou string, mas não é válido, se for array ou record, por exemplo.

Podemos contornar isto mudando < para uma função e colocá-la como parâmetro da coleção instanciada.

Definição do TAD com operador-parâmetro: collection binary_search_tree is parameters: type item_type; procedure LESS_THAN(A,B: item_type): boolean; end parameters; export: ...; local: ... else if LESS_THAN(item, tree.root_node) then ... end local;

Assim, LESS_THAN é um parâmetro formal que compara dois valores do tipo item_type.

Para valores numéricos, poderia ser usada a função com <:

77

procedure LESS_THAN_FLOAT(X, Y: in float): boolean; begin return(X<Y); end LESS_THAN_FLOAT;

Para arrays ou records, uma definição mais complexa seria necessária. Suponha que gostaríamos de definir duas ordens distintas para um tipo REG_EMPREGADO:

procedure LESS_NAME(X,Y: in REG_EMPREGADO): boolean; begin return X.NAME < Y.NAME; end LESS_NAME;

e procedure LESS_ID((X,Y: in REG_EMPREGADO): boolean; begin return X.ID < Y.ID; end LESS_ID;

Então poderíamos instanciar duas coleções diferentes com os comandos: collection bst_by_name is bst(REG_EMPREGADO, LESS_NAME); collection bst_by_id is bst(REG_EMPREGADO, LESS_ID);

e declarar as variáveis: X: bst_by_name.bst; Y.bst_by_id.bst;

X é uma ABB de registros de empregados, ordenada por nomes, e Y é uma ABB de registros de empregados, ordenada pelo campo ID.

8.1.5.4. TAD em ADA

package: construção básica de abstração de dados; segue o modelo collection.

Exemplo 1: package rng is {visíveis a unidades externas} function random return float; seed: integer; [private <decl.>] end rng; package body rng is {retém valor de uma chamada de random para outra} modulus: integer := 65536; mult: integer := 13849; addon: integer := 56963; function get_time_in_seconds ... function random return float is begin seed := (seed * mult + addon) mod modulus; return float(seed)/float(modulus);

78

end random; begin {procedimento de inicialização} seed := get_time_in_seconds mod modulus; {get_time_in_seconds é local - não visível por unidades externas} end rng;

Exemplo 2: package sem parte body package employee is type employee_record is record name: string(1..20); id: integer; address: string(1..20); pay_rate: float; end record; end employee;

Efeito: A inclusão desse package tornaria disponível esse tipo de dado a uma unidade de execução externa.

Exemplo 3: PRIVATE TYPES - escondendo informações das unidades externas. package binary_search_tree is type bst is private; {só o nome do tipo é visível} private type bst is access node; type node is record {visíveis no body, mas não externamente} root_item: item_type; left: bst; right: bst; end record; end binary_search_tree; package body binary_search_tree is ...

Operações permitidas sobre variáveis de um tipo privado: atribuição, testes para igualdade e desigualdade mais operações visíveis definidas pelo package.

Um package pode ser incluído numa biblioteca de usuário, na forma compilada, e ficar disponível via comando

with <package_name>;

Nomes visíveis do package devem vir precedidos pelo prefixo <package_name>. Isso pode ser evitado, através do comando

use <package_name>;

Exemplo 4: tree: binary_search_tree.bst;

ou

79

use binary_search_tree; tree: bst;

8.1.5.5. TAD em MÓDULA 2

Módulo: unidade de compilação separada que consiste de 2 partes: definição e implementação. Cada parte pode estar em arquivos separados.

Módulo de definição: contém listas de objetos EXPORTADOS e IMPORTADOS.

Módulo de implementação: contém definições completas de todos os procedimentos e tipos escondidos, que são exportados pelo módulo. Também contém definições de objetos locais. Variáveis locais são own: retém valor durante a vida ativa do módulo.

Um bloco de inicialização pode ser especificado, e é executado quando o módulo é importado por outro módulo.

Parametrização: O parâmetro formal ARRAY OF WORD é compatível com qualquer tipo de parâmetro atual. A função HIGH, aplicada a um elemento desse tipo, constrói o tamanho do elemento em palavras. Assim, Módula 2 cria uma unidade executável que aceita qualquer tipo como parâmetro - ao invés de criar uma unidade para cada tipo distinto.

8.1.5.6. TAD em C

Encapsulamento através do uso de source files e de declarações de variáveis extern e static que são não-locais a source files.

Objetos declarados como static num source file não são visíveis por outros arquivos. Mas não há maneira de exportar um nome de um tipo de dado e esconder seus componentes, por exemplo. Assim, esse mecanismo não é, de fato, uma abstração de dados.

Comunicação entre arquivos é possível via objetos declarados como extern. Objetos declarados fora de um bloco, como static, são locais ao source file. Aqueles declarados como extern são importados. Os demais são candidatos a serem exportados.

Objetos podem ser: funções, variáveis e constantes. Tipos não são exportáveis mas devem ser redefinidos em cada source file onde são usados.

8.1.5.7. Exercícios

1. Quais as vantagens e desvantagens do uso da forma de definição de tipo comparada com a forma de coleções, para definição de TAD?

2. TAD parametrizado é um conceito muito interessante, mas causa situações complexas durante o tempo de execução - notadamente no modo de coleções. Por que?

3. Quais seriam possíveis parâmetros para os TAD:

a) pilhas

b) polinômios

80

c) cadeias de caracteres

4. Considere a especificação do TAD em ADA, que implementa um conjunto (set): generic size: integer; type set_type is private; package SET_PACK is type set is private; function new_set return set; procedure add (s: in out set; item in set_type); function member (s: in set; item: in set_type) return boolean; procedure delete (s: in out set; item in set_type); private type set is array (1..size) of set_type; end SET_PACK; Use esse package para o seguinte:

a) Declare 2 conjuntos int_set e float_set, que são conjuntos de 100 inteiros e 50 reais, respectivamente. Faça isso usando e não usando o comando "use". Isso faz diferença?

b) Use as declarações acima e reescreva um fragmento de programa que soma 10 int_set e 1.5 a float_set. Faça usando e não usando o comando "use". Isso faz diferença?

c) Acrescente uma nova função; chame-a member, que verifica se 2 elementos distintos estão num conjunto (tome 3 argumentos). "Member" é um nome permitido? Por que?

8.1.6. Tratamento de Exceções

Quando um bloco de comandos é chamado implicitamente, pela ocorrência de uma condição, ao invés de explicitamente, pela chamada de um procedimento.

Definição: Uma exceção é uma condição que requer a ação imediata por parte do programa. Essa condição pode ser um erro (overflow, índice fora do intervalo, etc) ou não (fim de arquivo, por exemplo).

Exceções são condições síncronas, ao contrário de interrupções geradas pelo usuário ou dispositivos de E/S (assíncronas).

Procedimentos chamados implicitamente pela ocorrência de uma exceção são chamados de exception handlers. Assim como nos procedimentos, a chamada de um exeption handler resulta na suspensão da execução da unidade que chamou.

Ao término de um exception handler podem ser tomadas duas ações:

a) retomada da unidade suspensa

b) suspensão definitiva (terminação) da unidade que chamou.

A única diferença entre procedimentos e exception handlers é que esses últimos são chamados implicitamente. Os primeiros poderiam causar o mesmo efeito, através de condicionais. Por exemplo, o seguinte comando poderia ser incluído antes de cada acesso a um array:

if índice índice_máx or índice < índice_min

then fora_intervalo_handler;

Conseqüência: sobrecarga no texto do programa fonte.

Das linguagens de programação mais conhecidas, apenas PL/I e ADA incluem essa facilidade.

Chamada implícita: "Raising the Exception". Não existe sintaxe.

Algumas linguagens permitem que se chame explicitamente os handlers em pontos em que a condição não tenha sido satisfeita (comando RAISE <nome_handler>).

8.1.6.1. Tipos de exceções:

• Built-in

• Definidas pelo usuário

As definidas pelo usuário não possuem condições associadas e, por isso, só podem ser chamadas explicitamente. Assim, seu comportamento é similar ao de um procedimento. No entanto, a diferença entre eles é quanto ao fluxo de controle na terminação do handler.

8.1.6.2. Habilitação & Desabilitação de Exceções

O principal fator para desabilitar uma exceção é o custo em tempo e espaço X efeito.

A desabilitação ocorre em tempo de compilação e previne a geração do código que implementa a

81

82

exceção.

Escopo: blocos.

Exception handlers são ligados a uma exceção. Os Built-in têm um binding de nome que é parte permanente da linguagem de programação. Já os definidos pelo usuário têm o binding de nome ocorrendo durante a declaração, que é válido no escopo da declaração.

O binding da exceção com seu handler pode seguir dois modelos:

1) Via declaração na unidade. Redeclaração em outra unidade provoca novo binding. (ADA)

Exemplo: procedure A; E1, E2: exception; handler E1 is <H1 Bloco> handler E2 is <H2 Bloco> procedure B; E3: exception; handler E4 is <H4 Bloco> handler E3 is <H3 Bloco> begin <Bloco comandos de B> __handler E1 é H4 __handler E2 é H2 __handler E3 é H3 end; __B Begin __A __handler E1 é H1 __handler E2 é H2 End.B;

2) Via comandos executados dentro do bloco que podem modificar o binding exceção-handler. (PL/I)

Exemplo procedure A; E1, E2: exception; procedure B; E3: exception; begin __handler E1 é H1 (1a chamada) ou H4 (2a chamada) __handler E2 é H2 (1a chamada) ou H5 (2a chamada) set handler E3 to <H3 Bloco>

83

__handler E3 é H3 ... set handler E1 to <H4 Bloco> __handler E1 é H4 ... end; __B begin __A set handler E1 to <H1 Bloco> __handler E1 é H1 set handler E2 to <H2 Bloco> __handler E2 é H2 ... B; __handler E1 é H1 ... set handler E2 to <H5 Bloco> __handler E2 é H5 B; end; __A

8.1.6.3. Fluxo de controle quando do término de um Handler

Existem 3 alternativas:

1) Termina a unidade que invocou a exceção, retornando à unidade que a ativou.

2) Reativa execução da unidade que invocou a exceção, em ponto imediatamente seguinte ao comando que provocou a exceção.

3) Termina execução da unidade que chamou (como em 1) e ativa a mesma exceção na unidade que a chamou.

Qual alternativa adotar?

1. Mesma alternativa default para todos os casos;

2. Cada exceção, uma alternativa;

3. LP oferece construções para especificar a alternativa dentro do handler.

Quando não há handler definido para uma exceção que ocorreu:

• handler default é associado, ou

• exceção é propagada

84

8.1.6.4. Propagação de Exceção

Se uma exceção é ativada quando não existe handler associado, a unidade corrente é terminada e a mesma exceção é ativada pela unidade que a chamou. Se existe handler nessa unidade, ele é executado; caso contrário ela é terminada e a exceção é novamente propagada para níveis da árvore de chamadas dinâmicas. Se não existe handler até no nível da raiz, então o handler default é executado e o programa principal é terminado.

O registro de ativação (R.A.) de uma unidade contém uma lista de exceções built-in e definidas pelo usuário, com ponteiros para os respectivos handlers.

Condições de término:

1) Efeito de RETURN - desempilha R.A.

2) Efeito idêntico ao de um procedimento

8.1.6.5. Tratamento de Exceções em ADA

ADA possui 5 exceções built-in:

CONSTRAINT_ERROR: violação de range, índice ou discriminante.

NUMERIC_ERROR: se o resultado de uma operação numérica está fora do range de seu tipo.

PROGRAM_ERROR: quando uma unidade não disponível é chamada.

STORAGE_ERROR: quando não há memória para alocação dinâmica.

TASKING_ERROR: quando um erro ocorre durante a execução concorrente de tarefas (tasks).

Exceções definidas pelo usuário:

<ident_list>: exception;

RAISE [<exception_name>]

SUPPRESS <check_ident>

regras usuais de escopo

8.1.6.6. Exercícios

1. Exemplifique algumas exceções para as quais cada uma das 4 políticas de término, quanto ao fluxo de controle, seria apropriada:

a) Termina a unidade e retorna à unidade que a chamou;

b) Termina a unidade e invoca a exceção na unidade que a chamou;

c) Retorna a execução tentando novamente o comando onde a exceção foi ativada;

d) Retorna a execução imediatamente após o comando que causou a exceção.

2. Em ADA, a desabilitação de exceções ocorre em tempo de compilação. Que vantagens teria nessa ação em tempo de execução? E que dificuldades de implementação isso traria?

8.1.7. Concorrência

Habilidade de executar vários processos ao mesmo tempo.

Como isso pode ser representado numa LP?

Existem várias configurações de processadores para prover capacidade de concorrência:

1. Multiprogramação: usada por um sistema "time-sharing": um processador compartilhado por diversos processos, com estes executados alternativamente, sob controle do sistema operacional.

2. Multiprocessamento: vários processadores disponíveis, tal que múltiplos processos possam ser executados em processadores próprios. Memória comum compartilhada para comunicação entre os processos.

3. Processamento Distribuído: também requer processadores separados, mas cada um com sua memória. Processadores conectados via linhas de comunicação.

Para efeito de características de LP, a configuração usada é irrelevante. A LP provê um método abstrato para visualização e implementação de concorrência, que pode ser implementada em qualquer configuração, embora algumas construções possam se adaptar melhor numa determinada configuração.

Unidades Concorrentes = outra forma de abstração procedimental.

Diferença: a unidade que chama a unidade concorrente prossegue sua execução, sem esperar pelo término desta.

Mesmas considerações que outros procedimentos.

8.1.7.1. Definição

Existem dois modelos:

1) Definição por declaração: Quando a unidade concorrente é declarada, um binding é feito entre o nome e o corpo executável. A unidade pode então ser chamada concorrentemente por uma referência apropriada a seu nome.

85

2) Definição de tipo: A definição cria um tipo, e não um processo. Variáveis declaradas como tendo esse tipo têm seus nomes ligados a unidades executáveis, e múltiplas execuções do mesmo tipo podem ser referidas por nomes distintos.

8.1.7.2. Chamada

Pode ser implícita ou explícita:

Implícita:

Assume que o processo concorrente pertence à unidade em que está definido.

Sempre que uma unidade começa sua execução, todas as unidades concorrentes que pertencem a ela (são definidas naquela unidade) começam sua execução simultaneamente. Quando a unidade principal termina, ela espera até que todas as suas unidades concorrentes terminem, para retornar para quem chamou.

Exemplo: procedure P; concurrent unit C1; concurrent unit C2; begin ... end;

Explícita:

Exatamente como procedimentos: pode ser chamada em qualquer ponto dentro da unidade em que foi definida.

8.1.7.3. Compartilhamento de Dados e Comunicação Inter-Processsos

Existem dois modelos para compartilhamento de dados entre unidades executando concorrentemente: via memória compartilhada e via comunicação inter-processos (IPC).

Memória compartilhada: Neste caso, os dados compartilhados são globais a todas as unidades. Podem ser na forma de variáveis ou estruturas constantes, que todas as unidades podem usar.

Problema potencial: Conflito de Acesso.

86

Exemplo: (linguagem Pascal-like) program concurrent; var N: integer; {variavel global} concurrent unit P1; begin N := N + 1; end; concurrent unit P2; begin N := N + 2; end; begin {p.p.} N := 3; cobegin P1; P2; {chamadas explícitas} coend; writeln (N); end.

A prevenção deste tipo de problema com variáveis compartilhadas é uma das principais aplicações de sincronização.

Comunicação Inter-Processos (IPC): A IPC requer um message sender e um message reciever. Existem dois modelos de intercâmbio de informação: mail model e phone model. A diferença entre eles está em se o reciever precisa atender à mensagem antes do sender prosseguir.

8.1.7.3.1. Mail Model

No mail model, o Sender envia a mensagem e não espera por mensagem de recebimento para prosseguir.

87

S manda mensagem para R, colocando-a no Mail box. S prossegue com sua execução e R pode vir e retirar sua mensagem em qualquer momento posterior. Se mais de uma mensagem é enviada para o mesmo mail box, elas são usualmente enfileiradas dentro do mail box, tal que R pode retirar sucessivamente mensagens até esvaziar o mail box.

Há três versões para o mail model:

1) Muitos-para-Um

O Sender especifica o Reciever, mas este não precisa especificar o Sender.

2) Um-para-Um

O sender deve especificar o Reciever; o Reciever deve especificar o Sender. Um dado mail box é então identificado pelos dois processos (Sender e Reciever).

3) Muitos-para-Muitos

O Sender não especifica o Reciever; O próximo processo a pegar mensagem do mail box passa a ser o Reciever.

8.1.7.3.2. Phone Model

88

89

No phone model, o Sender espera que o Reciever receba a mensagem antes de prosseguir sua execução.

Existem duas formas dependendo do tipo de espera de recebimento:

1) Sender espera apenas pela notificação de recebimento do Reciever e então ambos continuam com as respectivas execuções concorrentes.

2) "Remote Procedure Call": o Sender espera pela mensagem de recebimento e pelo processamento da mensagem. Análogo a chamada de procedimento.

O phone model também possui três versões: 1 p/ 1, 1 p/ N e N p/ N.

8.1.7.4. Sincronização

Necessária quando:

• Dois ou mais processos compartilham o mesmo recurso que apenas 1 pode acessar por vez (Exclusão Mútua). Ex: compartilhamento de dados. Afim de proteger a integridade do dado apenas a um processo é permitido o acesso por vez.

• Um processo deve esperar que outro opere sobre um recurso comum, afim de que ele possa prosseguir (Dependência Mútua). Ex: Um processo coleta dados; outro opera sobre eles.

LPs especificam sincronização através de dois modelos gerais: Token e Gate (portão).

8.1.7.4.1. Token

Um único processo pode possuir um (hipotético) token por vez. Há duas operações: get_token e replace_token.

Se um processo P executa um comando get_token sobre o token T, e esse token não pertence a qualquer outro processo no momento, então o processo P torna-se o dono de T. Se T já é de algum outro processo, P é suspenso e deve esperar até que o token fique disponível novamente.

Geralmente, quando há vários processos esperando pelo mesmo token, forma-se uma fila FIFO.

O processo que possui o token pode executar o comando replace_token, passando-o ao primeiro processo da fila. Se a fila estiver vazia, o token fica disponível.

Exclusão Mútua e Dependência Mútua.

8.1.7.4.2. Gate

Um portão (hipotético) bloqueia a execução de um processo. Esse processo só pode prosseguir se outro processo abrir o portão. Existem dois comandos primitivos: wait e open.

Quando um processo executa um wait, ele espera "no portão" até que esse seja aberto; depois disso, continua sua execução. Se mais processos já estão esperando no portão, ele é colocado no final da fila.

Quando um processo executa um open, ele permite que o primeiro processo da fila retome sua

90

execução, e os demais se movem para frente, em primeira posição. Depois que um processo passa pelo portão, este se fecha novamente, até um novo open.

Se nenhum processo estiver esperando, nenhuma ação é tomada. O portão se abre e imediatamente se fecha, com nenhum processo passando por ele. Em qualquer dos casos, o processo que executou o open continua sua execução.

Uma forma adicional de sincronização é automaticamente implementada pelo phone model de passagem de informação, que requer que processos Sender e Reciever estejam sincronizados antes do envio da mensagem. É o método usado em ADA.

8.1.7.5. Concorrência em ADA

Unidades concorrentes: tasks (tarefas) definidas na parte declarativa de uma unidade de programas.

Tarefa: dividida em duas partes: especificação e corpo.

Especificação dá o nome da tarefa e lista todas as entradas usadas dentro da tarefa, bem como seus parâmetros formais. "Entradas" são o mecanismo de comunicação inter-processos - IPC - de ADA.

Corpo da tarefa é como qualquer outra unidade, consistindo de uma parte declarativa para definir objetos locais, e uma com comandos executáveis.

Em ADA, a definição de uma tarefa define um tipo do nome especificado, que pode ser ligado a variáveis daquela unidade.

Quando a palavra type é omitida, uma única tarefa é criada com aquele nome e nenhuma declaração de variável é necessária.

A chamada de uma tarefa, em ADA, é implícita. Todas as variáveis tarefas definidas na parte declarativa de uma unidade (chamada master) começa a ser executada simultaneamente com a master.

A unidade master pode ser:

• um bloco • um procedimento • uma função

A execução da master não termina até que todas as suas tarefas concorrentes não tenham terminado.

8.1.7.5.1. Compartilhamento de Dados

O Compartilhamento de Dados pode ser via memória compartilhada ou IPC.

Memória compartilhada é empregada através de variáveis globais. Uma vez que as tarefas compartilham o mesmo ambiente que as unidades master, elas têm acesso a todos os dados do ambiente master. Mas esse acesso a dados compartilhados deve ser cauteloso: a ordem em que diferentes tarefas acessam os dados é indeterminada a menos que alguma forma de sincronização seja utilizada.

A melhor forma de compartilhamento de dados é através do mecanismo IPC chamado entradas

91

(entries).

ADA usa a semântica da chamada remota de procedimento para implementar IPC: comportamento análogo à comunicação muitos-para-um do phone model.

Entradas têm parâmetros que são passados da mesma forma que parâmetros de procedimentos - IN, OUT e IN/OUT.

Uma entrada é chamada pela especificação da tarefa-destino, do nome da entrada e dos parâmetros atuais. A tarefa-destino solicita uma entrada por um comando ACCEPT, que recebe os parâmetros que podem então ser usados no bloco de comandos associado com o comando ACCEPT. Dependendo dos modos de passagem de parâmetro, a comunicação pode se dar em um ou nos dois sentidos.

A sincronização associada com entradas segue o phone model. A tarefa-Sender faz a chamada a uma tarefa específica. Se esta tarefa (reciever) estiver aguardando num comando ACCEPT para a entrada chamada, então ambas as tarefas podem prosseguir na execução, sendo que a tarefa-Reciever executa os comandos associados ao comando ACCEPT. Se, por outro lado, a tarefa-Reciever não estiver esperando no ponto do comando ACCEPT que "casa" com aquela chamada, então a tarefa-Sender é colocada numa fila de espera.

Exemplo 1: task ONE; task body ONE is X, Y: integer; begin CALCULATE(X); {espera até que TWO alcance ACCEPT} TWO.MEET(X, Y); {sincroniza com TWO, enviando X e retornando Y} USE_IT(X, Y); {usa X e Y depois do MEET} end ONE; task TWO is entry MEET(A: in integer; B: out integer); end TWO; task body TWO is Z: integer; begin OBTAIN(Z); {aguarda até chamada de MEET que case} accept MEET(A: in integer; B: out integer) do B := FN1(A, Z); Z := FN2(A, Z); end MEET; {retorna valor de B} display(Z); end TWO;

Exemplo 2: interação entre tarefa e master procedure TEST_TASKS is package INT_IO is new INTEGER_IO(integer); use INT_IO; task TEST; task body TEST is COUNT: integer; begin TEST for COUNT in 1..5 loop put (COUNT); put_line(" "); end loop; end TEST; begin TEST_TASKS for MAIN_COUNT in 6..10 loop put(MAIN_COUNT); put_line(" "); end loop; end TEST_TASKS; Este programa imprime as seqüências 1, 2, 3, 4, 5 e 6, 7, 8, 9, 10, mas as duas listas são

intercaladas na saída, em ordem indeterminada. Por exemplo, uma execução poderia resultar na saída: 1 6 7 2 3 8 9 4 5 10.

8.1.7.5.2. Esperas Seletivas

ADA usa o mecanismo de espera seletiva para aceitar uma, de várias sincronizações pendentes.

Suponha uma tarefa esperando por qualquer um, de vários comandos ACCEPT, ser chamado.

Analogia: recepcionista com vários telefones, pronta para atender qualquer um. Quando um toca, ela atende; se vários tocam ao mesmo tempo, ela escolhe um, ao acaso, para atender.

92

93

O comando SELECT permite que uma tarefa espere sobre vários "ACCEPTs". Quando um é chamado, seu handler (bloco de comandos) associado é executado, e a tarefa continua após o comando SELECT. Se mais de um é chamado simultaneamente, um é escolhido de maneira indeterminada.

A forma do SELECT é: select accept MEET_1 do handle1; end MEET_1; or accept MEET_2 do handle2; end MEET_2; or accept MEET_3 do handle3; end MEET_3; end select; Essa construção espera até que pelo menos uma das alternativas ACCEPT seja chamada. Se

nenhuma for, vai esperar para sempre. ADA provê um modo de evitar essa situação, através do comando DELAY. Por exemplo:

select accept MEET_1 do handle1; end MEET_1; or delay 1.0; handle2; end select; Essa construção espera até 1 segundo por MEET_1 ser chamada. Se não for, handle2 será

executado e a tarefa continua após o comando select.

Um caso especial de DELAY é ELSE que tem o efeito de DELAY 0.0. É escolhido se não houver chamada pendente por ocasião da execução do SELECT.

8.1.7.5.3. Guardas

ADA permite o uso de guardas como condições alternativas numa espera seletiva.

Analogia: Telefonista com telefones ligados em determinados dias da semana.

Exemplo: select when day_of_week = MONDAY accept PHONE_1 do handle1; end PHONE_1; or

94

when day_of_week = WEDNESDAY accept PHONE_2 do handle2; end PHONE_2; or when cot_dollar > 1.0 accept PHONE_3 do handle3; end PHONE_3; end select;

As guardas são avaliadas uma única vez, na entrada de select. Essa avaliação determina as alternativas abertas. A primeira alternativa que tem uma guarda aberta é então executada. A escolha é aleatória se mais de uma é chamada simultaneamente. Se todas as guardas são falsas, e uma alternativa else não for especificada, uma exceção é ativada.

Chamadas de entradas também podem ser sincronizadas em ADA. Por exemplo: select TASK_1.MEET_1; or delay 5.0; NO_ANSWER_ACTION; end select;

Irá esperar 5 segundos para TASK_1 executar um accept para a entrada MEET_1. Se isso não ocorrer, o procedimento NO_ANSWER_ACTION será chamado, e a tarefa prosseguirá adiante do select.

Uma espera de 0 segundos pode ser expressa por: select TASK_1.MEET_1; else NO_ANSWER_ACTION; end select;

NO_ANSWER_ACTION será chamado se TASK_1 não estiver esperando num accept para MEET_1 no momento em que a chamada à entrada é feita.

8.1.7.6. Exercícios

1. Em vários lugares, em processamento concorrente, uma fila é usada (verifique). Examine a possibilidade de se usar filas de prioridade (onde o processo de origem determina uma prioridade à atividade que é enfileirada). Como isso poderia ser útil? Como poderia ser implementado?

2. Um problema adicional surge no modelo TOKEN para o problema da exclusão mútua. Se 2 processos requerem o token simultaneamente, e o token é um recurso compartilhado, pode haver um problema de exclusão mútua associado, no acesso ao token. Descreva como isto poderia acontecer e como seria evitado?

3. Descreva como você simularia o modelo GATE para sincronização, usando apenas operações de TOKEN.

95

4. Idem para TOKEN e operações de GATE.

5. Algorítmos do tipo Divisão-e-Conquista podem ser implementados concorrentemente, de maneira natural. Descreva como um Quicksort concorrente poderia ser escrito usando qualquer um dos modelos vistos.

6. Dado o programa ADA TASKING_MANIA a seguir, quais das sequências de saída são legais? with TEXT_IO; use TEXT_IO; procedure TASKING_MANIA is task t1; task t2 is entry entry1; end t2; task t3 is entry entry1; end t3; task body t1 is begin select t2.entry1; else put_line ("t1.dying"); terminate end select; put_line ("t1: entry call accepted"); end t1; task body t2 is begin accept entry1; put_line ("t2: accepted entry1"); t3.entry1; accept entry1; put_line ("t2: accepted entry1 again"); end t2; task body t3 is begin t2.entry1; accept entry1; put_line ("t3: accepted entry1"); end t3; begin null; -- p.p. precisa de pelo menos 1 comando nulo end TASKING_MANIA; a) t2: accepted entry1 t1: dying t3: accepted entry1 t2: accepted entry1 again b) t2: accepted entry1

96

t1: entry call accepted c) t2: accepted entry1 t3: accepted entry1 t1: entry call accepted t2: accepted entry1 again d) t1: dying t2: accepted entry1 t3: accepted entry1 t2: accepted entry1 again e) t1: entry call accepted t2: accepted entry1 t3: accepted entry1 t2: accepted entry1 again

8.2. O Paradigma Funcional de LP

O modelo Funcional focaliza o processo de resolução do problema. A visão funcional resulta num programa que descreve as operações que devem ser efetuadas para resolver o problema.

Linguagens Funcionais: LISP

8.2.1. O Paradigma Funcional

Função:

f: {domínio} {contra-domínio}

Programa:

p: {possíveis entradas} {possíveis saídas}

Uma função tem três componentes básicos: domínio: conjunto de objetos aos quais a função se aplica, contra-domínio: conjunto de objetos resultantes da aplicação da função, e definição: especificação de como um objeto do contra-domínio é determinado a partir do domínio.

Há um quarto componente opcional: o nome da função.

Exemplo 1: função double

Domínio: Z

Contra-Domíno: Z

Definição: x + x, onde x é elemento do domínio

Nome: double

Notação matemática: double(x) = x + x

double: integer integer

Note que o operador + é usado na definição, e + é uma função também.

97

Uma notação mais consistente seria então: +(x, y), indicando uma função (+) com domínio o conjunto de pares de inteiros.

Expressão Lambda (Church - 1941)

Definição de função:

[<nome_função>] l <lista nomes elementos>.<definição>

Ex: double l x.x + x

Aplicação da função:

<especificador_função>:<valor_domínio> , onde especificador_função pode ser o nome ou a definição da função.

Ex: Aplicar a função double ao valor 2: double: 2 ou lx.x + x: 2

Exemplo 2: função max

max: integer x integer integer

max lx, y.if x y then x else y

max:<4, 2

Exemplo 3: função abs

abs lx.max:<x, -x>

abs: 6 max: <6, -6> if 6 > -6 then 6 else -6 6

Composição de funções:

Notação matemática: f(x) = abs(double(x))

Expressão Lambda: definimos essa composição por um novo operador:

f abs o double

isto é f é equivalente a sucessivas aplicações de double e abs: f: -3 abs: double: -3 abs: -3 +-3 abs: -6 if -6 > 6 then -6 else 6 6

8.2.2. FP: uma linguagem funcional pura (Backus-78)

Backus definiu uma classe geral de linguagens funcionais, das quais FP é um exemplo. A classe geral de linguagens tem quatro componentes básicos. Quando todos esses componentes são definidos, uma LPF particular é determinada. Esses componentes são:

1. Um conjunto de objetos atômicos, a partir do qual um conjunto mais geral de objetos pode ser construído.

2. Uma operação que aplica uma função a um objeto.

98

3. Um conjunto de funções primitivas, cada uma mapeando objetos em objetos, e que não podem ser definidas em termos de outras funções.

4. Um conjunto de formas funcionais que são funções que mapeam uma seqüência de funções e objetos em uma nova função.

Especificando esses quatro componentes de FP:

Objetos: O conjunto de objetos (para qualquer linguagem na classe de linguagens funcionais) é definido como o conjunto de objetos atômicos, seqüência de objetos, e um objeto indefinido, denotado por ^. Na notação BNF, um objeto é

<objeto> ::= ^ | <objeto_definido>

<objeto_definido> ::= <átomo> | <seqüência_objeto>

<seqüência_objeto> ::= <<objeto_definido> {, <objeto_definido>} >

No caso de FP, o conjunto de objetos atômicos consiste de seqüências não-vazias de caracteres, formada por dígitos, letras maiúsculas, e outros símbolos especiais. O caracter f é usado para representar a seqüência vazia e é considerado como um átomo e como uma seqüência. Os átomos especiais T e F são usados para representar true e false.

Exs: ^ A C12 12 <4, A, B> <7, <A, B>, 20>

Operação de Aplicação: A aplicação de uma função a um objeto é expressa pelo especificador da função seguido de : e do especificador de objetos. O especificador de função pode ser ou um nome de função ou a definição de uma função. Uma função pode, então, se aplicar apenas a um objeto, mas este objeto pode ser uma seqüência de objetos, portanto permitindo domínios mais complexos.

Exs: max : <4, 2> + : <3, 7> cat : <<4, A>, <C, D>>

Funções primitivas: Conjunto de funções primitivas de FP pode ser dividido em quatro grupos. Para toda função primitiva, f: ^ ^. Além disso, se for impossível aplicar uma função a determinado objeto (cuja estrutura não coincide com a definida), o resultado também será ^.

Tabela 1 - Funções primitivas de FP.

Seleção e estrutura simples

i: <x1, ..., xn> ≡ xiSeleção do i-ésimo

componente 2:<a, <b, c>, d> ≡ <b, c>

tl: <x1, ..., xn> ≡ <x2, ..., xn> Seleção da cauda tl:<a, <b, c>, d> ≡<<b, c>, d>

cons: <x, <x1, ..., xn>> ≡ <x, x1, ..., xn> Constrói uma seqüência

de um objeto e outra seqüência

cons:<a, <b, c>> ≡ <a, b, c>

length: <x1, ..., xn> ≡n Tamanho de uma seqüência length:<a, <b, c>, d> ≡ 3

id: x ≡ x Função identidade id:a ≡ a

99

Aritméticas (p/ seqüências numéricas)

+: <x, y> ≡ x + y Adição +:<4, 2> ≡ 6

-: <x, y> ≡ x - y Subtração -:<4, 2> ≡ 2

x: <x, y> ≡ x x y Multiplicação x:<4, 2> ≡ 8

/: <x, y> ≡ x / y Divisão /:<4, 2> ≡ 2

: <x, y> ≡ T se x y F caso contrário Maior que :<4, 2> ≡ T

Booleanas (Resultado é T ou F)

atom: x ≡ T se x é átomo Testa se é átomo atom:<a, b> ≡ F

null: x ≡ T se x é f Testa se é seqüência nula null:<a, b> ≡ F

eq: <x, y> ≡ T se x = y Testa se é igual eq:<a, a> ≡ T

and: <x, y> ≡ T se ambos são T and and:<T, F> ≡ F

or: <x, y> ≡ T se um é T or or:<T, F> ≡ T

not: x ≡ T se x é F not not:F ≡ T

Seleção e estrutura complexa

distl: <y, <x1, ..., xn >> ≡ <<y, x1>, ..., <y, xn>> Distribui à esquerda distl:<a, <b, c, d>> ≡ <<a, b>,<a, c>,<a, d>>

distr: <<x1, ..., xn>, y> ≡ <<x1, y>, ..., <xn, y>> Distribui à direta distr:<<b, c, d>, a> ≡ <<b, a>, <c, a>, <d, a>>

apndl: <y, <x1, ..., xn>> ≡ <y, x1, ..., xn> Acrescenta à esquerda apndl:<a, <b, c, d>> ≡ <a, b, c, d>

apndr: <<x1, ..., xn>, y> ≡ <x1, ..., xn, y> Acrescenta à direita apndr:<<b, c, d>, a> ≡ <b, c, d, a>

tlr: <x1, ..., xn> ≡ <x1, ..., xn-1> Cauda a partir da direita tlr:<a, b, c> ≡ <a, b>

ir: <x1, ..., xn> ≡ xn-i+1i-ésimo elemento a

partir da direita 2r: <a, b, c, d> ≡ c

Formas funcionais: São sete. Uma forma funcional cria uma nova função a partir de uma tupla de elementos, que podem ser funções ou objetos.

Tabela 2 - Formas funcionais de FP.

f1of2: x ≡ f1:(f2:x) Composição 2otl:<a, b, c, d> ≡ c

≡ n Constante : x ≡ 1 para todo x

[f1, ..., fn]:x ≡ <f1: x, ..., fn>: x Construção [+, x, 2]: <3, 2> ≡ <5, 6, 2>

αf: <x1, ..., xn> ≡ <f: x1, ..., f: xn> Aplicar a todos α+: <<3, 1>, <5, 3>, <6, 6>> ≡ <4, 8, 12>

/f: x ≡ x1 se x = <x1> f: <x1, /f: <x2, ..., xn>> caso contrário

Inserção /+: <2, 4, 3, 7> ≡ 16

(p → f; g): x ≡ f: x se p: x = T g: x se p: x = F Condição (>o [1, 2] → -; +): <5, 3> ≡ 8

(while p f): x ≡ x se p = F (while p f): (f: x) se p = T While (while atomo1 tl): <a, b, <c, d>, e> ≡ <<c, d>, e>

100

Uma notação adicional permite fazer um binding de um nome a uma função: Def l ≡ r, onde l é identificador de função e r é definição de função.

Ex: Def geq ≡ or o [gtr, eq]

Def leq ≡ not o gtr

Def lss ≡ not o geq

Def neq ≡ not o eq

Programas FP são, portanto, definições de funções, e suas execuções são aplicações das funções a objetos.

Veja alguns exemplos de programas em FP:

Exemplo 1 - Fatorial

Função definida com o auxílio de 3 funções auxiliares.

1ª) Testa se parâmetro atômico é zero: Def eq0 ≡ eq o [id, ] 2ª) Decrementa de 1 o parâmetro:

Def dec ≡ - o [id, ] 3ª) Determina se parâmetro é positivo

Def pos ≡ gtr o [id, ] Com essas 3 funções, mais a definição recursiva fatorial(x) = 1; x = 0

n 1

0

1

0

= x * fatorial(x - 1); x > 0 = indefinido; x < 0 podemos construir a função fatorial FP como:

Def fact (eq0 → ; (pos → x o [id, fact o dec]; ^)) 1Exemplo:

fact: 3 x o [id, fact o dec]: 3 x: <3, fact: 2> x: <3, x o [id, fact o dec]: 2 > x: <3, x: <2, fact: 1>> x: <3, x: <2, x o [id, fact o dec]: 1>> x: <3, x: <2, x: <1, fact: 0>>> x: <3, x: <2, x: <1, : 0>>> x: <3, x: <2, x: <1, 1>>> x: <3, x: <2, 1>> x: <3, 2> 6

Exemplo 2 - Append

append: <<a, b, c>, <d, e>> ≡ <a, b, c, d, e> Construímos uma função auxiliar que torna a função append mas simples de expressar. Essa

função é moveleft que pega o primeiro componente da seqüência à direita e move-o como último componente da seqüência à esquerda.

moveleft: <<x1, ..., xn>, <y1, ..., ym>> ≡ <<x1, ..., xn, y>1, <y2, ..., ym>> Usando a função primitiva apndr, que concatena um elemento à direita de uma seqüência:

Def moveleft ≡ [apndr o [1, 1 o 2], tl o 2] Exemplo:

moveleft: <<a, b>, <c, d>> [apndr o [1, 1 o 2], tl o 2]: <<a, b>, <c, d>> <apndr o [1, 1 o 2]: <<a, b>, <c, d>>, tl o 2: <<a, b>, <c, d>>> <apndr: <<a, b, c>, <d>>> <<a, b, c>, <d>>

A função append passa a ser definida pela repetição de se mover um componente à esquerda até que a segunda seqüência fique nula:

Def append ≡ 1 o (while not o null o 2 moveleft) Ou seja: "Enquanto o segundo parâmetro não for nulo, mova um componente da segunda para a

primeira seqüência, e selecione o primeiro parâmetro deste resultado."

Exemplo: append: <<a, b, c>, <d, e>> 1 o (while not o null o 2 moveleft): <<a, b, c>, <d, e>> 1 o (while not o null o 2 moveleft): <<a, b, c, d>, <e>> 1 o (while not o null o 2 moveleft): <<a, b, c, d, e>, f>

101

102

1: <<a, b, c, d, e>, f> <a, b, c, d, e>

Exemplo 3 - Consultar Base de Dados

Suponha uma BD representada por uma seqüência de pares chave/registro, e dada uma chave, queremos construir uma função que retorna o correspondente registro.

Exemplo:

BD: <<371489622, <BOSS, BIG>>, <274379811, <MANAGER, MIDDLE>>, <379421488, <WORKER, WILLING>>> Então:

search: <<...>, 379421488> ≡ <WORKER, WILLING>

BD

Vamos, primeiro construir uma função que aplicada a um par chave/registro e a uma chave, retorna uma seqüência que é o registro da chave, se esta casa com o segundo parâmetro, ou retorna a seqüência nula, caso contrário. Ou seja,

select: <<ki, ri>, k> ≡ <ri> if k = ki f caso contrário ou

Def select ≡ (eq o [2, 1 o 1] tl o 1; f) Agora, definimos search como:

Def search ≡ (/append) o (a select) o distr • distr - cria pares da chave procurada com cada par chave/registro da BD. • (a select) - aplica select a todos os componentes, resultando numa seqüência de seqüências

nulas mais seqüência com o(s) registro(s) correspondente(s) à chave, se houver matching. • (/append) - append todos os componentes resultantes. Exemplo:

BD chave search: <<<1, A>, <2, B>, <3, C>>, 2> (/append) o (a select) o distr: <<<1, A>, <2, B>, <3, C>>, 2> (/append) o (a select): <<<1, A>, 2>, <<2, B>, 2>, <<3, C>, 2> (/append): select: <<1, A>, 2>, select: <<2, B>, 2>, select: <<3, C>, 2> (/append): <f, <B>, f> append: <f, (/append): <<B>, f>> append: <f, append: <<B>, (/append): <f>>> append: <f, append: <<B,f>> append: <f, <B>> <B>

8.2.3. LISP (LISt Processing)

Linguagem funcional criada em 1960, por John McCartly do grupo de IA do MIT, para dar suporte à pesquisa em Inteligência Artificial. Foi inicialmente desenvolvida para o IBM 704. Existem muitos dialetos pois LISP nunca foi padronizada. Porém, em 1981 surgiu o Common LISP que é um padrão informal. Os programas em LISP são listas.

8.2.3.1. Componentes básicos

Objetos: dois tipos: átomos e listas.

Átomos - representados por cadeias de caracteres. Átomos numéricos são caracteres numéricos.

Listas - representada por uma seqüência de átomos e listas, separados por brancos e delimitados por parênteses (equivalente a seqüência de FP).

Exs: (x y z)

(plus 14 12)

(x (a b c) ( ) )

( ) ou nil - lista vazia (é átomo e lista)

Modelo de Implementação: cell model usado para visualizar a implementação de listas em LISP.

103

Lista é equivalente a lista encadeada de células. O componente dado é um ponteiro para o átomo ou lista; a ligação aponta para o próximo componente da lista. O último componente da lista aponta para nil.

Terminologia LISP:

Exemplo:

CAR (a b c) = a

CDR (a b c) = (b c)

CAR ((a b) nil) = (a b)

CDR ((a b) nil) = (nil)

CAR (( ) (b c (d)) e) = ( )

CDR CDR (( ) (b c (d)) e) = (e)

Funções: são representadas por listas - S_expression

Forma geral:

(<nome-função> <1o. parâmetro> ... <último parâmetro>)

átomo átomo ou lista

Ex: (plus 6 9) = 6 + 9

(plus (times 3 4) (times 6 7)) = (3 * 4) + (6 * 7)

Funções pré-definidas: LISP sempre tenta avaliar uma lista como se ela representasse uma função. Assim, se queremos prevenir uma avaliação, isso deve ser especificado com a função especial quote.

Ex: (quote (plus 2 3)) indica ao interpretador LISP que a lista (plus 2 3) deve ser considerada como uma lista de 3 elementos, ao invés de ser avaliada como o elemento atômico 5.

Devido à alta freqüência de quote, ela é abreviada por ': '(plus 2 3)

A função ' é aplicada a parâmetros de funções quando eles devem ser considerados como dados e não como funções.

104

105

8.2.3.2. Aglumas Funções Pré-definidas

Funções CAR, CDR: para obter cabeça e cauda da lista respectivamente.

(car '(a b c)) = a - átomo (ou lista)

(cdr '(a b c)) = b - sempre lista

(cdr '(a)) = nil

(car (cdr '(a b c))) = b

(car '(cdr (a b c))) = cdr

Função CONS: para construir uma lista. Tem dois parâmetros: o 1o será o car da lista o 2o será o cdr.

(cons 'a '(b c)) = (a b c)

(cons '(a) '(b c)) = ((a) b c)

(cons (car X) (cdr X)) = X; qualquer lista não vazia X

Átomos especiais:

t - true

nil - false (em contexto de resultado lógico)

Quando uma função resulta sempre em t ou nil, ela é dita ser um predicado.

Ex: (atom 'a) = t

(atom '(a) = nil

(atom '(a b)) = nil

(equal '(a b) '(a b)) = t

(equal nil '( )) = t

(< 2 3) = t

(> 5 4) = t

(= 0 0) = t

Abreviando aplicações de CAR e CDR:

Ex: (cdr (cdr (car '((a b c) (d e))))) pode ser abreviado por:

(cddar '((a b c) (d e))) = (c)

Função cond: condicional

(cond (<test1> <result1>)

106

(<test2> <result2>)

...

(<testn> <resultn>)

)

A função condicional avalia as cláusulas <test> até que uma resulte não-nil. O valor da avaliação da <result> correspondente é o valor de retorno de cond. Se nenhuma resultar não-nil, o valor de cond é nil. Equivalência com if - elsif - elsif - ... - endif.

Função SETQ:

Todas as funções pré-definidas anteriores aderem ao modelo funcional. Não é o caso da função SETQ, que funciona como o equivalente à atribuição de variáveis, que é um conceito não-funcional.

Ex: (setq x '(a b)) define a variável x cujo valor é o valor avaliado de '(a b) - que é a lista (a b).

Variáveis podem assumir valores que são átomos ou listas.

O segundo argumento de setq pode ser também um átomo ou uma lista, que será avaliada como função - a menos se submetida à função QUOTE (').

Repare como x é "visto" pelo sistema após o comando (setq x '(a b))

x = (a b)

(car x) = a

(cdr x) = (b)

(null x) = nil

(atom x) = nil

(atom 'x) = t

(equal x '(a b)) = t

A função SETQ retorna o valor atribuído à variável.

Ex: (setq y '(a (b c))) = (a (b c))

O modelo funcional proíbe uma função ter qualquer efeito externo que não seja o retorno de seu valor. Nesse sentido, SETQ é uma violação, já que tem como efeito a preservação de um valor através de um nome associado a ele.

8.2.3.3. Definição de Funções

Como definir funções em LISP:

(defun <nome_função>

107

(<parâmetro 1> ... <parâmetro n>)

<corpo_função>

)

Assim, defun tem 3 parâmetros. O primeiro é atômico e é o nome da função; o segundo é uma lista de nomes (atômicos) de parâmetros formais; o terceiro é a expressão que deve ser avaliada quando a função é chamada.

O valor resultante de defun é o nome da função; mas o principal resultado da aplicação de defun é o efeito colateral, que associa o nome da função à sua definição (corpo da função). Os parâmetros são usados como variáveis locais no corpo. Mais variáveis locais podem ser criadas via SETQ.

Ex: (defun length (list)

(cond ((atom list) 0) (t (plus 1 (length (cdr list))))

) ) (length '(a b (c d))) = 3

Funções também podem ser definidas sem a associação de um nome: através do uso de expressões lambda. Forma geral:

(lambda (<parâmetro 1> ... <parâmetro n>)

<corpo_função> )

Esta forma torna a função disponível, sem nomeá-la.

Ex: ((lambda (list)

(cond ((atom list) 0) (t (plus 1 (length (cdr list))))

) ) '(a b (c d)) )

Os parâmetros são implementados por cópia. Assim, seja: (defun fun (test)

((setq test nil) (null test) )

) O valor nil atribuído a test não terá efeito no parâmetro atual. E fun sempre retorna t.

(setq x 'a) = a

108

(fun x) = t

x = a - x não é afetada.

Todas variáveis não parametrizadas numa função são consideradas globais ao ambiente de definição, na maioria dos dialetos LISP, embora alguns dialetos usem escopo dinâmico, permitindo que a função herde os bindings de nome do ambiente que chamou.

O corpo da função pode conter qualquer número de avaliações. Quando há mais que uma, o resultado da última será o valor de retorno da função.

Veja alguns exemplos de programas em LISP:

Exemplo 1 - Fatorial (defun fatorial (x)

(cond ((< x 0) nil) ((= x 0) 1) ( t (times x (fatorial (minus x 1)))) )

) Ex: (fatorial 6) = 720

Exemplo 2 - Quicksort

Vamos definir, inicialmente, duas funções auxiliares:

(keep_le n lmign)

(keep_gt n lman)

(onde n é um átomo numérico e lmign e lman são listas de átomos numéricos) que retornam uma lista contendo todos os elementos, do segundo argumento, menores ou iguais (maiores) que o átomo numérico do primeiro argumento.

(defun keep_le (x slist) (cond ((atom slist) nil)

(( (car slist) x) (keep_le x (cdr slist))) (t (cons (car slist) (keep_le x (cdr slist)))) ) ) (defun keep_gt (x slist)

(cond ((atom slist) nil) (( (car slist) x) (cons (car slist) (keep_gt x (cdr slist)))) (t (keep_gt x (cdr slist)))

) ) Com essas funções, podemos definir a função quicksort. A função pré-definida append é usada

para concatenar 2 listas numa única: (append list1 list2) retorna list = list1.list2 (defun quicksort (slist)

(cond ((null slist) nil)

109

(t (append (quicksort (keep_le (car slist) (cdr slist))) (cons (car slist) (quicksort (keep_gt (car slist) (cdr slist))))

) )

) ) Seja slist = (x ...) a função calcula a lista ordenada dos elementos menores ou iguais a x, em R e

então concatena-a com a lista formada por x mais os elementos maiores que x, ordenados.

Resultado de retorno = (......... x .........)

< ou = a x > que x (ordenados)

Exemplo 3 - Pilhas

Para implementar pilhas em LISP, nós as representamos por listas, cujo primeiro elemento é o do topo. Três funções são definidas então:

empty: stack → t se pilha vazia

nil se pilha não vazia

push: stack, element → newstack, que é stack com element adicionado ao topo

pop: stack → (element, newstack), onde newstack é stack com o topo (element) removido

Então: (defun empty (stack) (null stack) ) (defun push (stack element) (cons element stack) ) (defun pop (stack) (cond ((empty stack) nil) (t (list (car stack) (cdr stack))) ) ) List é similar a cons, porém aceita qualquer número de elementos e constrói uma lista com 1o

elemento sendo o 1o argumento; o 2o elemento sendo o 2o argumento; e assim por diante.

Veja a diferença entre list e cons:

(list 'a '(b c)) = (a (b c)) - o 2o argumento pode ser átomo ou lista

(cons 'a '(b c)) = (a b c) - o 2o argumento sempre é lista

110

8.2.3.4. Exercícios

1. Como visto, LISP viola o modelo funcional de várias formas. Quais as vantagens e desvantagens disto?

2. Tanto dados como funções são expressos como expressões-S em LISP. Qual seria a utilidade disso?

3. Como o conceito de TAD se aplica a LISP?

4. Considere uma matriz de números representada em LISP como uma seqüência de linhas, onde cada linha é também uma seqüência. Por exemplo, a matriz

1 2 3

4 5 6

7 8 9

é representada como: ('(1 2 3) '(4 5 6) '(7 8 9))

a) Escreva uma função LISP chamada "coluna1" que, tendo a matriz como parâmetro, produz a soma dos números da primeira coluna.

Exemplo: (coluna1 ('(1 2 3) '(4 5 6) '(7 8 9))) = 12

b) Generalize a solução do item a) escrevendo uma função coluna, cuja forma de aplicação é:

(coluna m c), onde m é a matriz e c é um inteiro indicando o número da coluna e que produz como resultado a soma dos números da coluna c da matriz m.

5. Escreva uma função LISP chamada rev que tem como entrada uma lista e retorna a lista invertida.

6. Utilizando o cell model dê a representação interna das seguintes listas:

a) ((b c) (x (y z)))

b) (a (b c) d e (f))

c) (a b (c (d e) f))

d) (a b (c)) d (e f)

8.2.4. Comparação de LISP com FP

LISP se afasta do modelo funcional puro em dois sentidos:

1- O uso do conceito de variável, que é do modelo imperativo, permitindo que nomes sejam ligados a valores durante a avaliação de uma função.

2 - A possibilidade de LISP especificar uma execução sequencial de funções (o corpo de uma função é uma seqüência de aplicações de funções). Isto não se inclui entre as formas funcionais de FP.

Ex: Considere uma lista de pares atributo/valor, do tipo:

((nome João) (sobrenome Silva) (idade 30) (sexo M))

Considere a tarefa de recuperar um valor a partir de uma chave. Na verdade, pode haver vários pares com a mesma chave. Ex: (irmão Carlos) (irmão José). A função getall retorna a lista de valores, dada uma chave.

(defun getall (alist aname) (let (result) (cond ((null alist) nil (t (setq result (getall (cdr alist) aname)) (cond ((equal (caar alist) aname) (setq result (cons (cadar alist) result)))) ) ) result - valor de retorno ) ) (let <lista_variáveis_locais> <função 1> <função 2> - escopo das variáveis locais ... <função n> ) Embora LISP possua várias características que violam as restrições de FP, ela contém

basicamente as propriedades fundamentais daquela linguagem.

O conceito de átomos e listas correspondem aos objetos de FP.

Uma aplicação de função, que em FP é denotada por f: <p1, ..., pn> é expressa em LISP por (f p1 ... pn)

As funções pré-definidas de LISP correspondem às funções primitivas de FP (ou podem ser facilmente definidas em LISP)

As formas funcionais de FP podem ser todas implementadas em LISP

Por exemplo:

composition

FP - f1 o f2:x

LISP - (f1 (f2 x))

constant

nFP -

LISP - 'n

construction

111

112

FP - [f1, ..., fn]: x LISP - (defun construct (flist x) (cond ((null flist) nil) (t (cons (funcall (car flist) x) (construct (cdr flist) x)) ) ) ) )

apply-to-all

FP - αf: x

LISP - (mapcar f x) - pré-definida

insertion

FP - /f: x

LISP - (apply f x) - pré-definida

conditional

FP - (p → f1; f2): x LISP - (defun conditional (p f1 f2 x) (cond ((funcall p x) (funcall f1 x)) (t (funcall f2 x)) ) )

while

FP - (while p f): x LISP - (defun while (p f x) (cond ((funcall p x) (funcall f x)) (while p f (funcall f x))) (t x) ) )

8.3. O Paradigma Lógico de LP

O modelo Lógico está relacionado à perspectiva da pessoa: ele encara o problema de uma perspectiva lógica. Um programa lógico é equivalente à descrição do problema expressa de maneira formal, similar à maneira que o ser humano raciocinaria sobre ele.

Linguagens Lógicas: PROLOG

113

8.4. O Paradigma Orientado a Objeto (OO) de LP

O modelo Orientado a Objeto focaliza mais o problema. Um programa OO é equivalente a objetos que mandam mensagens entre si. Os objetos do programa equivalem aos objetos da vida real (problema).

A abordagem OO é importante para resolver muitos tipos de problemas através de simulação.

A primeira linguagem OO foi Simula, desenvolvida em 1966 e depois refinada em Smalltalk. Existem algumas linguagens híbridas: Modelo Imperativo mais características de Orientação a Objetos (OO). Ex: C++.

No modelo OO a entidade fundamental é o objeto. Objetos trocam mensagens entre si e os problemas são resolvidos por objetos enviando mensagens uns para os outros.

Linguagens Orientadas a Objeto: SMALL TALK

8.4.1. Componentes básicos:

• Objetos: Um objeto é um conjunto encapsulado de operações e um estado que registra e lembra o efeito das operações. Um objeto executa uma operação em resposta ao recebimento de mensagem que está associada à operação. O resultado da operação depende do conteúdo da mensagem recebida e do estado do objeto quando ele recebe a mensagem. O objeto pode, como parte dessa operação, enviar mensagem para outros objetos e para si mesmo.

• Mensagens: São requisições enviadas de um objeto a outro, para que este produza algum resultado desejado. A natureza das operações é determinada pelo objeto receptor. Mensagens podem ser acompanhadas de parâmetros, que são aceitos pelo objeto receptor e que podem ter algum efeito nas operações realizadas. Esses parâmetros são, eles próprios, objetos.

• Métodos: São descrições de operações que um objeto realiza quando recebe uma mensagem. Uma mesma mensagem poderia resultar em métodos diferentes, quando enviada para diferentes objetos. O método associado com a mensagem é pré-definido. Um método é similar a um procedimento; mas há diferenças.

• Classes: Uma classe é um template para objetos. Consiste de métodos e descrições de estado que todos os objetos da classe irão possuir. Uma classe é similar a um TAD, no sentido de que ela define uma estrutura interna e um conjunto de operações que todos os objetos, que são instâncias da classe, possuirão. Uma classe é também um objeto, e portanto aceita mensagens e possui métodos e um estado interno. Uma mensagem que muitas classes aceitam é uma mensagem de instanciação, que institui a classe a criar um objeto que é um elemento ou instância da classe receptora.

8.4.2. Propriedades do Modelo Orientado a Objeto

• Encapsulamento: Cada objeto é visto como o encapsulamento de seu estado interno, suas mensagens, e seus métodos. A estrutura do estado e os pares mensagem-método são todos definidos pela classe à qual o objeto pertence. O valor do estado interno é determinado pelos métodos que o objeto executa em resposta às mensagens recebidas.

• Polimorfismo: É a propriedade que permite que uma mesma mensagem seja enviada a diferentes

114

objetos, sendo que cada objeto executa a operação apropriada à sua classe. Mais importante: o objeto que envia a mensagem não precisa conhecer a classe do objeto receptor ou como aquele objeto irá responder à mensagem. Isto significa que a mensagem, por exemplo, "print" pode ser enviada a um objeto, sem a preocupação se aquele objeto é um caracter, um inteiro, uma string ou uma figura. O objeto receptor irá responder com o método que for apropriado à sua classe.

• Herança: É uma das propriedades mais importantes do modelo OO. Ela é possível via a definição de hierarquia de classes, isto é, uma estrutura em árvore de classes, onde cada classe tem 0 ou mais subclasses. Uma subclasse herda todos os componentes de sua classe-pai, incluindo estrutura do estado interno e pares método-mensagem. Toda propriedade herdada pode ser redefinida na subclasse, sobrepondo-se à definição herdada. Esta propriedade encoraja a definição de novas classes, sem duplicação de código.

8.4.3. Exemplo em Hool:

Hool é uma linguagem OO hipotética. Class Account {Variáveis de cada objeto dessa classe} Instance Variables: balance, {saldo} monthlyReport, {extrato mensal} name, {nome do correntista} acctID {código da conta} {Variáveis da classe toda. Todos os métodos das instâncias podem se referir a elas} Class Variables: TotalBalance, NumberofAccts {Ativado por mensagens enviadas à classe} Class Methods: initialize {nome da mensagem a ser recebida} {mensagens enviadas a outros objetos} TotalBalance <- assign(Amount <- new). TotalBalance <- assign(0.00). NumberofAccts <- assign(Integer <- new). NumberofAccts <- assign(0). reportTotals TotalBalance <- print. {amount} NumberofAccts <- print. {integer} {ativados por mensagens enviadas à instância} Instance Methods: deposit (itemID, anAmount, date) TotalBalance <- addon(anAmount). balance <- addon (anAmount). monthlyReport <- addline('DEP', itemID, anAmount, date, balance). monthend monthlyReport <- print. {imprime extrato} open (id, custName) NumberofAccts <- addon(1). balance <- assign(Amount <- new). balance <- assign(0.00). monthlyReport <- assign(Report <- new).

name <- assign(custName). accID <- assign(id). monthlyReport <- titleline(id, custName). withdraw (itemID, anAmount, date) * (anAmount <- greaterThan(balance)) <- ifTrue ([errorReport <- append('overdrown access')]). (anAmount <- greaterThan(balance)) <- ifFalse ([balance <- subtract(anAmount). totalBalance <- subtract(anAmount). monthlyReport <- addline('WTH', itemID, anAmount, date, balance).]).

* <message> <- ifTrue (<block>)

Se mensagem é true então <block> é executado pelo objeto receptor, que é true ou false, quando executado. Ou seja, ifTrue e ifFalse são métodos de uma classe cujas instâncias resultam valores True ou False.

Variáveis de classe: classes determinadas dinamicamente pelas atribuições. Essas classes podem ser pré-definidas em HOOL ou definidas pelo usuário (como é o caso de Account). No caso do exemplo:

balance é da classe Amount nn.nn para representar quantias em dólar

monthlyReport é da classe Report: array de strings; cada string representa uma linha do extrato

name e acctID são da classe String

TotalBalance é da classe Amount e é a soma dos saldos de todas as instâncias dessa classe

NumberofAccts é da classe Integer e contém o número de contas (instâncias) da classe, que foram criadas.

Sintaxe para definição de um método:

115

116

método ::= <nome_mensagem> [(<id_par.> {, <id_par.>})] {<mensagem>}

A lista de parâmetros indica o número de objetos que serão recebidos junto com a mensagem, e os nomes pelos quais serão acessados no corpo do método. O corpo do método consiste de uma seqüência de mensagens enviadas a outros objetos; no formato HOOL:

message ::= <id.obj.receptor> <- <nome_msg> [(<par.> {, <par.>})]

Ex: TotalBalance <- assign(Amount <-new).

O resultado de uma mensagem é parâmetro de outra.

A mensagem new é enviada à classe de objetos Amount, que produz como resultado um novo objeto da classe Amount, ao qual não foi atribuído qualquer valor. Este novo objeto é então o parâmetro da mensagem assign e resulta na atribuição da variável de classe TotalBalance com o objeto Amount recém-criado.

TotalBalance <- assign(0.00).

resulta na atribuição de 0.00 ao objeto Amount recém instanciado, TotalBalance

Os métodos assign das linhas

8 TotalBalance <- assign(Amount <- new).

9 TotalBalance <- assign(0.00).

são completamente diferentes, uma vez que na linha 8 TotalBalance é da classe object - uma classe inicial de objetos que ainda não foram classificados. Trata-se de uma classe genérica, que reconhece um número mínimo de mensagens.

Na primeira mensagem assign, TotalBalance se torna uma instância da classe Amount.

A segunda mensagem (linha 9) ativa o método associado à mensagem assign na classe Amount, desde que essa é a classe do objeto receptor (TotalBalance). Isso ilustra o poder do polimorfismo.

Ilustrando Herança:

Vamos definir 2 subclasses de Account: Checking e Savings. Class Checking Superclass: Account Class Variables: MonthlyServiceCharge Class Methods: setServiceCharge(anAmount) MonthlyServiceCharge <- assign(anAmount) Instance Methods: check(item_ID, anAmount, date) self <- withdraw(item_ID, anAmount, date) monthend(date) self <- withdraw('SVC', MonthlyServiceCharge, date) self <- monthend

117

Class Savings Superclass: Account Class Variables: InterestRate Class Methods: setInterestRate(aRate) Instance Methods: monthend(date) |interest| {variável local do método} interest <- assign(balance). interest <- multiplyby(InterestRate). interest <- divideby(12). self <- deposit('INT', interest, date). self <- monthend.

8.4.4. Exemplo em C++:

C++ é uma LP híbrida: possui todos os recursos de C mais alguns recursos de OO.

Vantagens:

• uso de diferentes modelos para diferentes partes do problema

• reaproveitamento de código imperativo

C++ é um superconjunto de C. Módulos de C e C++ podem chamar uma ao outro.

Componentes de C++:

Classes:

Classe em C++ é uma extensão de estruturas (records) de C, somando-se a habilidade de se definir componente da estrutura que são funções. Logo uma definição de classe consiste de uma coleção de declarações de variáveis e funções. Cada declaração pode ser pública ou privada, seguindo o modelo de TAD: elementos públicos são visíveis fora da classe; privados não são.

Forma geral de definição de classe: class <nome_classe>{ <decl. privadas> public: <decl. públicas> }

Exemplo: class intervalo float esq, dir; /* variáveis privadas da classe */ public: /* construtor da classe */ intervalo(float e, float d) esq := e; dir := d;

118

float esquerdo() return esq; float direito() return dir; int contem(float f) return (f esq) && (f < dir); int contemint (intervalo x) return (esq < x.esquerdo()) && (dir x.direito());

Objetos:

São criados pela declaração a uma classe.

Exemplo: intervalo x;

O objeto criado, x, tem suas próprias variáveis privadas - esq e dir - bem como suas próprias funções - esquerdo, direito, contem e contemint. O construtor - intervalo - também pertence ao objeto.

A função intervalo é chamada para todo objeto que é declarado com dois parâmetros.

Por exemplo, x poderia ser inicializado como objeto intervalo (1.5, 2.8) pela declaração

intervalo x(1.5, 2.8)

Uma vez executada a declaração de um objeto, qualquer um de seus componentes públicos podem ser referidos, colocando-se seu nome como prefixo do objeto.

Exemplo:

x.esquerdo() /* refere-se ao limite do intervalo x à esquerda */

Isso significa uma chamada à função esquerdo, associada com o objeto x.

Os elementos na área privada do objeto são inacessíveis aos módulos externos.

No entanto, é possível permitir o acesso de uma função externa a elementos privados de uma classe, através da declaração dessa função como friend function, dentro da classe.

Exemplo: class intervalo { friend void printintervalo(intervalo); ... } /* fora da classe intervalo */ void printintervalo(intervalo x) { printf("(%f, %f)", x.esq, x.dir);}

Todas as funções de uma classe podem ser declaradas friends de uma dada classe, se a classe for declarada como friend.

Herança:

É implementada em C++ via o conceito de classe derivada. Uma classe derivada de uma classe-base herda todos os membros públicos da classe-base. Assim, a classe derivada é a sub-classe, e a classe-

119

base é a superclasse.

A declaração de uma classe derivada é da forma: class <nome_classe_derivada>: public <nome_classe_base> { <declarações classe derivada> }

Isso faz todos os membros públicos da classe base serem também membros públicos da classe derivada.

Uma variação disso é: class <nome_classe_derivada> : <nome_classe_base> { <declarações classe derivada> }

Nesse caso, os membros públicos da classe base são herdados como privados da classe derivada.

Note que os membros privados da classe base não podem ser herdados.

Funções Virtuais:

Se uma função numa classe_base é declarada virtual, então ela pode ser redefinida numa classe derivada.

Exemplo: class top { public: virtual print1() {printf("top1\n");} print2() {printf("top2\n");} print3() {printf("top3\n");} print() {print1(); print2()l print3();} }; class bottom: public top { public: /* herda print3 e print e redefine as demais */ print1() {printf("bottom1\n");} print2() {printf("bottom2\n");} }; main() { top t; bottom b; t.print(); b.print(); } Output: top1 top2 top3 bottom1

120

top2 // print2 não foi declarada virtual em top top3

Overloading:

C++ permite o overloading de operadores, funções e construtores.

Exemplo: classe Amount class amount { int tc; public: amount(int d, int c) {tc = 100 * d + c;} amount() {tc = 0;} amount operator * (int v) {return amount(tc * v / 100, tc * v % 100);} amount operator * (float v) {int newtc = v * tc + 0.5; return amount(newtc / 100, newtc % 100);} amount operator + (amount a) {int newtc = a.tc + tc; return amount(newtc / 100, newtc % 100);} amount operator - (amount a) {int newtc = a.tc - tc; return amount(newtc / 100, newtc % 100);} int operator > (amount a) {return tc > a.tc;} int cents() {return tc % 100;} int dollars() {return tc / 100;} void setamount(float f) {tc = f * 100.0;} void setamount(int d, int c) {tc = 100 * d + c;} void printamount() {printf("%d.", tc / 100); if (tc % 100 < 10) {printf("0");} printf("%d\n", tc % 100); } int totalcents() {return tc;} }

Dois construtores: um sem parâmetros, outro com dois parâmetros. Assim, um objeto pode ser declarado de duas maneiras:

amount a(5, 25); /* inicializa a com 5 dólares e 25 cents */ amount b; /* inicializa b com 0.00 */

Exemplo: classe Account #include <amount.h> amount totbal(0,0); int numofaccts = 0; class account { public: amount balance;

121

char monthlyreport[1000]; char name[30]; char acctD[10]; void addline(char* mr, char* type, char* D, amount amt, char* date, amount balance) void topline() account(char* ID, char* custname) {numofaccts++; balance.setamount(0, 0); *monthlyreport = 0; strcpy(name, custname); strcpy(acctID, ID); topline(); } void deposit(char* itemID, amount amt, char* date) {totbal = totbal + amt; balance = balance + amt; addline(monthlyreport, "DEP", itemID, amt, date, balance); } void withdraw(char* itemID, amount amt, char* date) {if (!(amt balance)) {balance = balance - amt; totbal = totbal - amt; addline(monthlyreport, "WTH", itemID, amt, date, balance); } void monthend() {printf(monthlyreport); *monthyreport = 0; } }

Exemplos: declarações e chamadas de funções para classe Account e Amount: account x("x1001", "Joao Silva"); amount a; a.setamount(100,0); x.deposit("011", a, "01/01/95");

Exemplol: Classes derivadas: checking e savings #include <amount.h> #include <account.h> amount monservchg(5, 0); float intrate = 6.0; class checking : public account { public: void check(char* itemID, amount amt, char* date) {withdraw(itemID, amt, date);} void monthend(char* date) {withdraw("SVC", monservchg, date); account::monthend(); }

122

} class savings : public account { public: void monthend(char* date) {deposit("INT", balance*(intrate/1200.0), date); account::monthend(); } }

8.4.5. Orientação a Objeto (OO) x Paradigma Imperativo (PI)

O conceito de classe no modelo OO é essencialmente idêntico ao de TAD (Tipo Abstrato de Dados). Ambos apresentam uma definição encapsulada de estruturas de dados e procedimentos, permitem esconder informações, e são instanciados em objetos.

A maior diferença está no fato de que classe provê polimorfismo e herança.

Polimorfismo em Linguagens Imperativas: em ADA existe o overloading de procedures ou operadores.

Herança em Linguagens Imperativas: em Pascal existem subtipos, que são limitados a tipos escalares.

8.4.6. Exercícios

1. Qual a diferença entre herança e o uso de "include files", como em C?

2. Compare linguagens orientadas a objeto puras com híbridas (C++), quanto as suas vantagens e desvantagens.

3. Projete um TAD para classe Amount e um para a classe Account.

4. Escreva definições de classes em C++ para:

a) uma pilha

b) uma fila

c) uma árvore binária de busca

d) intervalos abertos sobre os reais

Objetos da classe d) deveriam aceitar as mensagens:

left - retorna o último valor à esquerda

right - retorna o último valor à direita

in: aFloat - retorna TRUE se aFloat pertence ao intervalo

in: anInterval - retorna TRUE se anInterval está contido em intervalo

123

9. Bibliografia "Conceitos de Linguagens de Programação"; Ghezzi, C.; Jazayeri, M.; Rio de Janeiro; Campus;

1985

"Programming Languages: Structures and Models"; Dershem, H. L.; Jipping, M. J.; USA; Wadsworth Publishing Company; 1990

"Programming Language Landscape - Syntax/Semantics/Implementation"; Marcotty, M.; Ledgard, H.; 2a Ediçao; 1986

"Introdução ao FORTRAN 77 para Microcomputadores"; Cereda, R. L. D.; Maldonado, J. C.; São Paulo; McGraw-Hill; 1987

"Programação COBOL"; Bastos, A. C.; Livros Técnicos e Científicos Editora

"LISP"; Winston, P. H.; Horn, B. K. P.; Addison-Wesley; 1984

"Programação com BASIC"; Gottfried, B. S.; McGraw-Hill

"Programação em Pascal"; Gottfried, B. S.; McGraw-Hill

"PROLOG - A Logical Approach"; Dodd, T.; Oxford; Oxford University Press; 1990

"The C Programming Language"; Kernighan, B. W.; Ritchie, D. M.; New Jersey Prentice-Hall; 1978

"A Pratical Guide to ALGOL 68"; Pagan, F. G.; Wiley-Interscience; 1976

"Processamento Interativo: a linguagem de programação APL"; Zimmermann, C. J.; Rio de Janeiro; Livros Técnicos e Científicos; 1981

"Programming in Modula-2"; Wirth, N.; Springer-Verlag; 1985

"A Course of Algol 60 Programming"; Naur, P,; Copenhagen; 1962

"A Little Smalltalk"; Budd, T.; Addison-Wesley; 1987

"Simula Begin"; Birtwistle, G. M.; Dahl, O-J; Myhrhaug, B.; Nygaard, K.; Philadelphia; Auerbach; 1973

"ADA, uma introdução"; Ledgard, H.; Rio de Janeiro; Campus; 1985

"Introdução à programação com PL/I"; Furtado, A. L.; Passos, E. L.; Rio de Janeiro; Livros Técnicos e Científicos; 1978

"Common LISP, the Language", Guy L. Steele Jr., Scott E. Fahlman, Richard P. Gabriel, David A. Moon, Daniel L. Weinreb