Vous êtes sur la page 1sur 140

UNIVERSIDADE FEDERAL DO TOCANTINS

UNIVERSIDADE FEDERAL DO TOCANTINS

CAMPUS UNIVERSITARIO
DE PALMAS

CURSO DE CIENCIA
DA COMPUTAC
AO

- NOTAS DE AULA
LINGUAGENS DE PROGRAMAC
AO

Tiago Almeida

Palmas
Marco de 2016

Sum
ario
1 Introduc
ao
1.1 A arte do projeto de linguagens . . . . . . .
1.2 Spectrum das linguagens de programacao . .
1.3 Por que estudar linguagens de programacao?
1.4 Por que estudar linguagens de programacao?
1.5 Compilacao e interpretacao . . . . . . . . .
1.6 Uma revisao sobre a compilacao . . . . . . .
1.6.1 Fase de Analise . . . . . . . . . . . .
1.6.2 Fase Sntese . . . . . . . . . . . . . .
1.7 Definicoes basicas . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

1
1
1
4
4
6
8
8
10
11

2 Sintaxe das linguagens de programac


ao
16
2.1 Sintaxe das linguagens de programacao . . . . . . . . . . . . . . . . . . . . 16
2.2 Vinculacoes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3 Nomes, escopos e vinculac
oes
3.1 Significado dos nomes dentro de um escopo . .
3.2 A vinculacao do ambiente de referenciamento
3.3 Expansao de macros . . . . . . . . . . . . . .
3.4 Compilacao separada . . . . . . . . . . . . . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

26
26
29
33
33

4 An
alise sem
antica
34
4.1 Regras de analise semantica . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.2 Gramatica de atributos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.3 Avaliacao de atributos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
5 Fluxo de controle
5.1 Fluxo de controle . . . . . . . . . . .
5.2 Avaliacao de expressoes . . . . . . . .
5.3 Fluxo estruturado e nao estruturado
5.4 Sequencia . . . . . . . . . . . . . . .
5.5 Selecao . . . . . . . . . . . . . . . . .
5.6 Iteracao . . . . . . . . . . . . . . . .
5.7 Nao determinancia . . . . . . . . . .
6 Tipos de dados
6.1 Tipos de dados . . . . . . . .
6.2 Checagem de tipos . . . . . .
6.3 Layout da memoria . . . . . .
6.4 Dimensoes, limites e Alocacao

.
.
.
.

.
.
.
.

.
.
.
.

ii

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

.
.
.
.

.
.
.
.
.
.
.

42
42
43
49
51
51
54
57

.
.
.
.

58
58
58
61
63

7 Abstraco
es: Subtorinas e Controle
7.1 Sequencia de chamadas . . . . . .
7.2 Passagem de parametros . . . . .
7.3 Subrotinas e modulos . . . . . . .
7.4 Manipulacao de excecao . . . . .
7.5 Co-rotinas . . . . . . . . . . . . .
7.6 Eventos . . . . . . . . . . . . . .

da
. .
. .
. .
. .
. .
. .

8 Abstrac
ao de dados e orientac
ao `
a
8.1 Programacao orientada `a objetos
8.2 Encapsulamento e heranca . . . .
8.3 Inicializacao e finalizacao . . . . .
8.4 Metodo de vinculacao dinamica .
8.5 Heranca m
ultipla . . . . . . . . .
8.6 Revisao da orientada a` objeto . .

objetos
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .

9 Linguagens funcionais
9.1 Linguagens funcionais . . .
9.2 -expressoes . . . . . . . . .
9.3 Linguagem Haskell . . . . .
9.4 Funcoes . . . . . . . . . . .
9.5 Tipos de dados . . . . . . .
9.6 Metodologia de programacao
9.7 O tipo Lista . . . . . . . . .
9.8 Classes de tipos . . . . . . .
9.9 Tipos algebricos . . . . . . .
10 Linguagens l
ogicas
10.1 Linguagens logicas . . .
10.2 Linguagem Prolog . . . .
10.3 Fatos, regras e consultas
10.4 Listas . . . . . . . . . .
10.5 Aritmetica . . . . . . . .
10.6 Corte de fluxo . . . . . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

Refer
encias Bibliogr
aficas

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

Abstrac
ao
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

67
67
69
72
74
76
77

.
.
.
.
.
.

79
79
82
84
85
87
88

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

89
89
91
94
96
97
100
103
110
112

.
.
.
.
.
.

115
. 115
. 116
. 121
. 129
. 132
. 134

.
.
.
.
.
.

.
.
.
.
.
.

137

iii

1 Introdu
c
ao
1.1

A arte do projeto de linguagens

Por que existem tantas? Existem varias respostas possveis:


Evolucao - Os anos 1960 e incio dos anos 1970 viram uma revolucao na programacao estruturada, em que o fluxo de controle baseado goto deram lugar a locos
while, declaracoes case (switch) e construcoes similares de nvel mais alto. No final de 1980, a estrutura de blocos aninhados das linguagens comecou a dar lugar a
orientacao a` objetos;
Fins Especiais - Varias variacoes de Lisp sao boas para manipulacao de dados simbolicos e estruturas de dados complexas. Icon e Awk sao boas para manipulacao
de cadeias de caracteres. C e bom para programacao de sistemas em baixo nvel.
Prolog e bom para o raciocnio logico sobre relacoes de dados;
Preferencia pessoal - Algumas pessoas acham que e natural pensar de forma recursiva; outros preferem iteracao. Algumas pessoas gostam de trabalhar com ponteiros.
O que torna uma linguagem bem sucedida? Novamente, ha varias respostas [1]:
Expressividade;
Facilidade de uso;
Facilidade de implementacao;
Padronizacao;
Codigo aberto;
Economia e Inercia.

1.2

Spectrum das linguagens de programa


c
ao

Muitas lnguas existentes podem ser classificados em famlias com base em seu modelo
de computacao. A Figura ?? mostra um conjunto comum de famlias. A divisao de
nvel superior distingue entre as linguagens declarativas, em que o foco e sobre o que o
computador esta a fazer, e as linguagens imperativas, em que o foco e sobre a forma como
o computador deve faze-lo.

Declarativas
Funcional
Fluxo de Dados
Logica, baseada em restricoes
Baseada em Template
Imperativas
Von Neumann
Script
Orientada `a objetos
Figura 1.1

linguagens declarativas sao de alguma nvel mais elevadosentido; eles estao mais em
sintonia com o ponto de vista do programador, e menos com o ponto de vista do implementador. linguagens imperativas predominam, no entanto, principalmente por razoes de
desempenho. Ha uma tensao no projeto de linguagens declarativas entre o desejo de ficar
longe de irrelevantesdetalhes de implementacao, bem como a necessidade de permanecer
perto o suficiente para os detalhes para, pelo menos, controlar o esboco de um algoritmo.
O projeto de algoritmos eficientes, afinal, e o que grande parte da ciencia da computacao e
sobre. Ainda nao esta claro ate que ponto e em que problema domnios, podemos esperar
que os compiladores descobrir bons algoritmos para problemas considerados de muito alto
nvel de abstracao. Em qualquer domnio no qual o compilador nao consegue encontrar
um bom algoritmo, o programador precisa ser capaz de especificar uma forma explcita.
Dentro das famlias declarativas e imperativas, existem varios importantes
linguagens funcionais empregar um modelo computacional baseado na definicao recursiva de funcoes. Eles tomam a sua inspiracao a partir do calculo lambda, um
modelo computacional formal, desenvolvido por Alonzo Church em 1930. Em essencia, um programa e considerado uma funcao de entradas e sadas, definida em
termos de funcoes mais simples atraves de um processo de refinamento. Lnguas
nesta categoria incluem Lisp, ML e Haskell.
Dataflow computacao modelo lnguas como o fluxo de informacao (fichas) entre os
nos funcionais primitivas. Eles fornecem um modelo inerentemente paralelo: nodos
sao desencadeados pela chegada de sinais de entrada, e pode operar em simultaneo.
Id e Val sao exemplos de linguagens de fluxo de dados. Sisal, um descendente de
Val, e mais frequentemente descrito como uma linguagem funcional.
lnguas Logic- ou constrangimento a` base de tomar a sua inspiracao de logica de
predicados. Eles modelo computacional como uma tentativa para encontrar valores
2

que satisfazem certos relacionamentos especificado, usando a pesquisa dirigida a


objetivos atraves de uma lista de regras logicas. Prolog e a linguagem logica mais
conhecido. O termo tambem e aplicado a`s vezes a linguagem de banco de dados
SQL, a linguagem de script XSLT, e aspectos programaveis de planilhas como o
Excel e os seus antecessores
As lnguas de von Neumann sao os mais familiarizados e bem sucedida. Eles incluem Fortran, Ada 83, C, e todos os outros em que os meios basicos de computacao
e a modificacao de variables.7 Considerando linguagens funcionais sao baseados em
expressoes que tem valores, lnguas de von Neumann sao baseadas em declaracoes
(atribuicoes, em particular) que influenciam o calculo subsequente via o efeito colateral da alteracao do valor de memoria.
linguagens de script sao um subconjunto das lnguas de von Neumann. Eles se
distinguem pela sua enfase na colandoos componentes que foram originalmente
desenvolvidos como programas independentes. Varias linguagens de script foram
originalmente desenvolvidos para fins especficos: csh e bash, por exemplo, sao os
idiomas de entrada de programas de controle de trabalho (shell); Awk foi destinado
para a geracao de relatorios; PHP e JavaScript sao destinados principalmente para a
geracao de paginas web com conte
udo dinamico (com execucao no servidor eo cliente,
respectivamente). Outras lnguas, incluindo Perl, Python, Ruby e Tcl, sao mais
deliberadamente proposito geral. A maioria colocar enfase na prototipagem rapida,
com um vies em direcao a facilidade de expressao sobre velocidade de execucao.
linguagens orientadas a objeto tracar suas razes para Simula 67. A maioria estao estreitamente relacionadas com as lnguas de von Neumann, mas tem um modelo muito
mais estruturada e distribuda de memoria e computacao. Ao inves de computacao
imagem como o funcionamento de um processador monoltico em uma memoria
monoltico, linguagens orientadas a objeto imagina-lo como interacoes entre objetos
semi independentes, cada um dos quais tem tanto o seu proprio estado interno e
sub-rotinas para gerenciar esse estado. Smalltalk e a mais pura das linguagens orientadas a objeto; C ++ e Java sao os mais utilizados. Tambem e possvel conceber
linguagens funcionais orientada a objetos (o mais conhecido deles e o CLOS [Kee89]
extensao para Common Lisp), mas eles tendem a ter um forte sabor imperativo.
1 int gcd ( int a , int b ) { // C
2
while ( a != b ) {
3
i f (a > b) a = a b ;
4
else b = b a ;
5
}
6
return a ;
7 }

1 ( define gcd ; ; Scheme


2
(lambda ( a b )
3
( cond ((= a b ) a )
4
((> a b ) ( gcd ( a b ) b ) )
5
( e l s e ( gcd ( b a ) a ) ) ) ) )
1 gcd (A, B,G) : A = B, G = A. % P r o l o g
2 gcd (A, B,G) : A > B, C i s AB, gcd (C, B,G) .
3 gcd (A, B,G) : B > A, C i s BA, gcd (C, A,G) .

1.3

Por que estudar linguagens de programa


c
ao?

Entender as caractersticas obscuras. O programador de C++ raramente usa union,


heranca m
ultipla, n
umeros de argumentos variaveis .
Escolha entre formas alternativas para expressar as coisas, com base no conhecimento dos custos de implementacao. Com certos compiladores, pode ser necessario
adotar linguagens de programacao especiais para obter o codigo mais rapido.
Faca bom uso de depuradores, montadores, ligadores e ferramentas relacionadas.
Em geral, o programador de linguagem de alto nvel, nao precisa se preocupar com
detalhes de implementacao. Ha momentos, no entanto, que uma compreensao desses
detalhes prova-se extremamente u
til.

1.4

Por que estudar linguagens de programa


c
ao?

Simular caractersticas u
teis em linguagens que nao as possuem. Em dialetos mais
antigos do Fortran, por exemplo, programadores familiarizados com construcoes
modernas de controle pode usar comentarios e auto-disciplina para escrever codigo
bem estruturado. Em linguagens sem constantes nomeadas ou tipos de enumeracao,
variaveis que sao inicializadas uma vez e nunca mudam podem tornar o codigo muito
mais legvel e de facil manutencao.
Fazer melhor uso da tecnologia da linguagem onde quer que apareca. A maioria dos
programadores nunca vai projetar ou implementar uma linguagem de programacao
convencional, mas a maioria tera a tecnologia da linguagem para outras tarefas
de programacao. Um computador pessoal tpico contem arquivos em dezenas de
formatos estruturados, abrangendo o conte
udo da web, processamento de texto,
planilhas, apresentacoes, vetores graficos, m
usica, vdeo, bancos de dados e uma
grande variedade de outros domnios de aplicacao.
4

Caracterstica

Legibilidade

Criterios
Capacidade de Escrita

Confiabilidade

Simplicidade/ortogonalidade
Estruturas de Controle
Tipos de dados e estruturas
Projeto da sintaxe
Suporte para abstrac
ao
Expressividade
Verificac
ao de tipos
Manipulac
ao de excec
oes
Apelido (aliasing) restrito

Legibilidade
As linguagens devem possuir u
nica e exclusivamente elementos de facil entendimento e nao ambguos.
Simplicidade e ortogonalidade a linguagem deve prover um n
umero reduzido
de elementos basicos. A linguagem deve conter um n
umero mnimo de primitivas que possam ser combinadas.
Instrucoes de controle que nao comprometam a clareza dos programas as
instrucoes de controle devem ser reduzidas ao conjunto estritamente essencial
para o controle de fluxo de execucao dos programas.
Facilidade para representacao de tipos e estruturas de dados - a linguagem deve
fornecer facilidades para a representacao de dados comuns para a resolucao de
problemas.
Sintaxe limpa e concisa cada instrucao deve representar de forma u
nica e
intuitiva o seu significado.
Capacidade de escrita
Programas facilmente entendidos sao provenientes da facilidade de escrita fornecidas pela linguagem.
O suporte a abstracoes podem devem ser fornecidos para facilitar a programacao.
Funcoes em linguagens imperativas sao formas abstratas de representar solucoes
nas quais os parametros abstraem os dados reais a serem manipulados.
Confiabilidade
Espera-se que solucoes dadas aos problemas sejam confiaveis.
desejavel que os programadores nao facam operacoes com tipos conflitantes.
E

A manipulacoes de excecoes tem sido cada vez mais utilizadas nas linguagens
de programacao atuais.
Custo
O custo associado a uma linguagem vai desde o custo de desenvolvimento ate
sua amortizacao proveniente da aceitacao no mercado e os custos relativos ao
seu uso.
Linguagens legveis, de facil escrita e confiaveis tendem a ter um custo mais
baixo de treinamento e sao mais aceitas no mercado.

1.5

Compilac
ao e interpreta
c
ao

No nvel mais alto de abstracao, a compilacao e execucao de um programa em um alto


nvel olhar linguagem algo como isto:
Programa Fonte

Compilaldor

Entrada

Programa Alvo

Sada

Figura 1.2

O compilador traduz o programa fonte de alto nvel em um programa de destino


equivalente (normalmente em linguagem de maquina), e depois vai embora. Em algum
momento posterior arbitraria, o usuario informa ao sistema operacional para executar o
programa de destino. O compilador e o locus de controle durante a compilacao; o programa
de metas e o locus de controle durante a sua propria execucao. O compilador e em si um
programa em linguagem de maquina, presumivelmente, criado atraves da compilacao de
algum outro programa de alto nvel. Quando gravados em um arquivo em um formato
entendido pelo sistema operacional, linguagem de maquina e comumente conhecido como
codigo-objeto.
Um estilo alternativo de execucao para linguagens de alto nvel e conhecido como
interpretacao.
Ao contrario de um compilador, um interprete fica em torno para a execucao do
pedido. Na verdade, o interprete e o locus de controlo que durante a execucao. Com
efeito, o interprete implementa uma maquina virtual cuja linguagem de maquina e a
6

Programa Fonte

Interpretador

Sada

Entrada

Figura 1.3

linguagem de programacao de alto nvel. O interprete le declaracoes em que a linguagem


mais ou menos um de cada vez, executando-os como ele vai junto.
Em geral, a interpretacao leva a uma maior flexibilidade e melhores diagnosticos (mensagens de erro) do que a compilacao. Porque o codigo fonte esta sendo executado diretamente, o interprete pode incluir um excelente depurador de nvel de fonte. Ele tambem
pode lidar com idiomas no qual as caractersticas fundamentais do programa, tais como
os tamanhos e tipos de variaveis, ou mesmo que nomes referem-se quais as variaveis, pode
depender dos dados de entrada. Alguns recursos de linguagem sao quase impossveis de
implementar sem interpretacao: em Lisp e Prolog, por exemplo, um programa pode escrever novas pecas de si e executa-los em tempo real. (Varias linguagens de script, incluindo
Perl, TCL, Python e Ruby, tambem oferecem esse recurso.) O adiamento de decisoes sobre a execucao do programa ate que o tempo de execucao e conhecida como a ligacao
tardia;
Compilation, pelo contrario, geralmente leva a um melhor desempenho. Em geral,
uma decisao tomada em tempo de compilacao e uma decisao que nao precisa ser feita em
tempo de execucao. Por exemplo, se o compilador pode garantir que variavel x sera sempre
estao no local 49378, que pode gerar instrucoes em linguagem de maquina que acessam
este local sempre que o programa fonte refere-se a x. Por outro lado, um interprete pode
precisar de olhar x em uma mesa de cada vez que e acessado, a fim de encontrar a sua
localizacao. esde a (versao final de um) programa e compilado apenas uma vez, mas
geralmente executar muitas vezes, a poupanca pode ser substancial, especialmente se o
interprete esta fazendo um trabalho desnecessario em cada iteracao de um loop.
Embora a diferenca conceptual entre compilacao e interpretacao e claro, a maioria das
implementacoes de linguagens incluem uma mistura de ambos. Eles geralmente parecido
com este:
Nos geralmente dizer que uma lngua e interpretado quando o tradutor inicial e simples. Se o tradutor e complicado, dizemos que a lngua e compilada. A distincao pode
ser confuso porque simples e complicado sao termos subjetivos, e porque e possvel que
um compilador (tradutor complicado) para produzir codigo que e em seguida, executado
por uma maquina virtual complicado (interprete); este e, de facto, precisamente o que

Programa Fonte

Tradutor

Programa
Intermediario

Maquina Virtual

Sada

Entrada

Figura 1.4

acontece por padrao no Java. Nos ainda dizer que uma lngua e compilado se o tradutor analisa-lo completamente (ao inves de efetuar alguma transformacao mecanica), e
se o programa intermediario nao tem uma forte semelhanca com a fonte. Essas duas
caractersticas-minuciosa analise e transformacao nontrivial-sao as marcas de compilacao.

1.6

Uma revis
ao sobre a compila
c
ao

A partir do diagrama na proxima pagina, voce pode ver, existem duas principais fases
do processo de compilacao: analise e sntese. A etapa de analise rompe-se o programa
de origem em pedacos, e cria um (linguagem independente) representacao intermediaria
generico do programa. Em seguida, a fase de sntese constroi o programa alvo desejado
a partir da representacao intermedia. Normalmente, uma fase de analise compilador e
chamado de sua extremidade dianteira ea sntese palco para a sua extremidade traseira.
Cada uma das fases e dividido em um conjunto de fases que lidam com diferentes partes
das tarefas. (Por que voce acha que compiladores tpicos separar o processo de compilacao
em frente e back-end fases?)

1.6.1

Fase de An
alise

Ha quatro fases na fase de analise de compilacao:


1. Analise Lexical: O fluxo de caracteres que compoem um programa de codigo e lido
da esquerda para a direita e agrupados em tokens, que sao sequencias de caracteres
que tem um significado coletivo. Exemplos de smbolos sao identificadores (nomes
definidos pelo usuario), palavras reservadas, inteiros, duplos ou carros alegoricos,
delimitadores, operadores e smbolos especiais.
8

Programa Fonte
Analise Lexica
Analise Sintatica
Analise
Analise Semantica
Geracao de Codigo
Intermediario
C
odigo Intermediario

Tabela de Smbolos

Otimizacao de C.I.

Sntese

Geracao de
C
odigo Objeto
Otimizacao de C.O.
Programa Alvo

Figura 1.5

2. Analise Sintatica: Os tokens encontrados durante a varredura sao agrupados usando


um contexto livre gramatica. A gramatica e um conjunto de regras que definem
as estruturas validas na linguagem de programacao. Cada token esta associado a
uma regra especfica, em agrupados em conformidade. Este processo e chamado de
analise. A sada desta fase e chamada uma arvore de analise ou uma derivacao, ou
seja, um registro de quais regras gramaticais foram usadas para criar o programa
fonte.
3. Analise semantica: A arvore de analise ou derivacao e proxima marcada por erros
semanticos, ou seja, uma declaracao de que e sintaticamente correto (associa com
uma regra de gramatica corretamente), mas desobedece as regras semanticas da
lngua de origem. Analise semantica e a fase onde nos detectar tais coisas como o uso
de uma variavel nao declarada, uma funcao chamada com argumentos improprios,
violacoes de acesso, e operandos incompatveis e digite descasamentos, por exemplo,
uma variavel de matriz adicionada a um nome de funcao.
Example of semantic analysis:
1 int a r r [ 2 ] , c ;
2 c = arr 10;

Analise mais semantica diz respeito `a verificacao de tipos. Embora o fragmento C


acima fara a varredura em tokens validos e com sucesso combinar as regras para
uma expressao valida, nao e semanticamente valido. Na fase de analise semantica,
o compilador verifica os tipos e relatos de que voce nao pode usar uma variavel de
matriz em uma expressao de multiplicacao e que o tipo do lado direito da atribuicao
nao e compatvel com a esquerda.
4. Intermediate Geracao de Codigo: Este e o lugar onde a representacao intermediaria
do programa de fonte e criado. Queremos que esta representacao para ser facil de
gerar, e facil de traduzir para o programa de destino. A representacao pode ter uma
variedade de formas, mas um comum e chamada de tres codigo de endereco (TAC),
que e muito parecido com uma linguagem generica de montagem que nao obriga a
uma arquitetura particular. Codigo de tres enderecos e uma seq
uencia de instrucoes
simples, cada um dos quais pode ter no maximo tres operandos.
Au
nica declaracao C a` esquerda e traduzido em uma sequencia de quatro instrucoes
em tres codigo de endereco a` direita. Note o uso de variaveis temporarias que sao
criadas pelo compilador e necessaria para manter o n
umero de operandos ate tres.
Claro, e um pouco mais complicado do que isso, porque temos que traduzir Desvios
e ciclos instrucoes, bem como chamadas de funcao. Aqui estao algumas TAC para
uma traducao ramificacao:

1.6.2

Fase Sntese

Pode haver ate tres fases na fase de sntese de compilar:


1. Intermediate otimizacao de codigo: O otimizador aceita entrada na representacao
intermediaria (por exemplo, TAC) e produz uma versao simplificada ainda na representacao intermediaria. Nesta fase, o compilador tenta produzir o menor, resultado
de corrida mais rapida e eficiente atraves da aplicacao de varias tecnicas, tais como
suprimir a geracao de codigo de segmentos de codigo inacessvel,
livrando de variaveis nao utilizadas,
eliminando a multiplicacao por 1 e adicao de 0,
Otimizacao da malha (por exemplo, retire declaracoes que nao sao modificados
no circuito),
eliminacao subexpressao comum,
etc.

10

A fase de otimizacao pode realmente abrandar um compilador, por isso a maioria dos
compiladores permitir que esse recurso a ser suprimido ou desativado por padrao. O
compilador pode ate ter graos pequenos controles que permitem que o desenvolvedor
para fazer a melhor combinacao entre o tempo gasto a compilacao versus qualidade
de otimizacao.
No exemplo mostrado acima, o otimizador foi capaz de eliminar uma adicao a`
zero e uma reavaliacao da mesma expressao, permitindo que o original cinco TAC
declaracoes de ser reescrita em apenas tres declaracoes e usar menos duas variaveis
temporarias.
2. Objeto Geracao de Codigo: Este e o lugar onde o programa de destino e gerado. A
sada desta fase e geralmente o codigo de maquina ou codigo de montagem. Locais de
memoria sao selecionados para cada variavel. As instrucoes sao escolhidos para cada
operacao. O codigo de tres endereco e convertido em uma seq
uencia de montagem
ou de linguagem de maquina instrucoes que executam as mesmas tarefas.
3. Objeto Optimization cupom: Tambem pode haver uma outra passagem de otimizacao que se segue a geracao de codigo, desta vez transformando o codigo objeto em
mais apertado, codigo objeto mais eficiente. Este e o lugar onde nos consideramos
caractersticas do proprio hardware para fazer uso eficiente do processador (s) e registradores. O compilador pode tirar proveito de linguagens especficas de maquina
(instrucoes especializadas, pipelining, previsao de desvios, e outras otimizacoes peephole) na organizacao e racionalizacao do proprio codigo de objeto. Tal como acontece
com otimizacao IR, esta fase do compilador geralmente e configuravel ou pode ser
ignorada por completo.

1.7

Definic
oes b
asicas

Os dados sao caracterizados por tres aspectos basicos:


Valores sao representacoes simbolicas de conceitos.
Os valores relevantes para a resolucao de um problema sao classificados segundo
algum criterio, e uma classe de valores recebe o nome de um tipo.
Como um programa e composto por mais de uma expressao, os valores dos dados
precisam ser registrados temporariamente ou permanentemente para passar de
uma expressao para outra. Os registros desses valores servem como repositorio para
passagem de valores, e eles sao efetuados em variaveis.
Numericos
11

A linguagem Pascal embute uma representacao de n


umeros inteiros e de pontoflutuante que sao denotados por Integer e Real:
1 var i : Integer ;
2
r : Real ;

Na linguagem C, estes mesmo mecanismos sao descritos pelas palavras reservadas


int e float
1 int i ;
2 float r ;

Nao-numericos
Uma variavel b booleana e uma c caractere sao representadas por:
1 var b : Boolean ;
2
c : Character ;

Uma variavel caractere c e representada em C por:


1 char c ;

Nao-numericos
Na linguagem Pascal, um apontador para um valor inteiro pode ser definido como:
1 var in tP : Integer ;

De forma analoga, na linguagem C, um apontador para um valor pode ser definido


por:
1 int intC ;

Enumerados
Em Pascal, por exemplo, podemos criar um tipo (conjunto de valores) para representar os meses do ano:
1 type MesesP = ( jane , f e v e , marc , a b r i , maio , junh , j u l h , agos , s e t e
, outu , nove , d e z e ) ;

De forma analoga, podemos ter em C este novo tipo definido por:


1 enum MesesC { jan , f e v , mar , abr , mai , jun , j u l , ago , s e t , out , nov ,
dez } ;

12

Produto cartesiano
O produto cartesiano do conjunto S pelo conjunto T e definido por

C = S T = {(x, y)|x S, y T }

(1.1)

O n
umero de elementos (cardinalidade) do conjunto C, denotado por #C corresponde a multiplicidade da cardinalidade de S pela de T

#C = #S #T

(1.2)

Produto cartesiano
Em Pascal:
1 type DataP = record
2
m: MesesP
3
d : DiasP
4
end ;

Em C:
1 struct DataC {
2
MesesC m
3
DiasC d
4
};

Uniao Disjunta
A uniao disjunta dos conjuntos S e T , denotada por S + T , e definida por

C = S + T = {prim x|x S} {seg y|y T }

(1.3)

Assim o conjunto resultado possui:

#C = #S + #T
Uniao disjunta
Em Pascal:
13

(1.4)

1 type P r e c i s a o = ( exato , aprox ) ;


2 NumeroP = record
3
case p r e c : P r e c i s a o of
4
e x a t o : ( i v a l : Integer ) ;
5
aprox : ( r v a l : Real ) ;
6
end ;

Em C:
1 union NumeroC {
2
int i v a l ;
3
float rval ;
4
};

Mapeamentos
O mapeamento do conjunto S para o conjunto T , denotado por S T , e definido

m : S T = {m|x S m(x) T }

(1.5)

A cardinalidade e definida por

#(S T ) = (#T )#S

(1.6)

Em Pacal:
1 var mapintP : array [ 0 . . 1 5 ] of Integer ;

Em C:
1 int mapintC [ 1 6 ] ;

Conjuntos potencia
O conjunto potencia de um dado conjunto S, denotado por P(S), e definido como
segue:

P(S) = {s|s S}
Ou seja, todos os subconjuntos podem ser formados pelas elementos de S
14

(1.7)

#(P(S)) = 2#S

(1.8)

A linguagem Pascal possui um construtor de conjuntos que nos permite construir


conjuntos de cores primarias, como:
1 type c o r e s = ( vermelho , a z u l , amarelo ) ;
2 NovasCores = set of c o r e s ;

Uma lista deve ser vista como um tipo recursivo e nao como um mapeamento.
Um tipo lista de inteiros pode ser definida pela sua unidade base, a lista vazia, juntamente com um inteiro seguido de uma lista de inteiros
IntList = U nit + (Integer IntList)

(1.9)

Podemos ainda redefinir a equacao:


IntList = nil cons(i, l)|i Integer, l IntList

(1.10)

Desmembrando em:
nil

(1.11)

cons(i, nil)|i Integer

(1.12)

cons(i, cons(j, nil))|i, j Integer

(1.13)

Em Pascal:
1 type I n t L i s t P = IntNode ;
2 IntNode = record v a l o r : Integer ;
3
prox : I n t L i s t P
4
end ;

Em C:
1 typedef struct N o I n t L i s t I n t L i s t C ;
2 struct N o I n t L i s t {
3
int v a l o r ;
4
I n t L i s t C prox ;
5
};

15

2 Sintaxe das linguagens de programa


c
ao
2.1

Sintaxe das linguagens de programa


c
ao

Linguagens de programacao devem ser precisas.


Tanto sua forma (sintaxe) e significado (semantica) deve ser especificado sem ambiguidade, de modo que ambos os programadores e os computadores possam dizer
o que um programa deve fazer.
Para proporcionar o grau necessario de precisao, projetistas da linguagem utilizam
uma notacao formal de sintatica e semantica.
Especificar a gramatica de uma determinada linguagem requer a utilizacao de um
conjunto de regras especficas;
Qualquer conjunto de strings que podem ser definidas em termos concatenacao,
alternativa e repeticao e chamado de um conjunto regular, ou linguagem regular;
Linguagens regulares sao geradas por express
oes regulares e reconhecidas por
analizadores lexicos;
Qualquer conjunto de strings que podem ser definidas se adicionarmos a recursividade e chamado de uma linguagem livre de contexto;
Linguagens livres de contexto sao geradas por gram
aticas livres de contexto e
reconhecidas por analisadores sintaticos.
Considere, por exemplo, a sintaxe das constantes numericas:

number integer|real
integer digit digit
real integer exponent|decimal(exponent|)
decimal digit (. digit|digit .)digit
exponent (e|E)(+||)integer
digit 0|1|2|3|4|5|6|7|8|9

16

As expressoes regulares funcionam para definir tokens. Eles sao incapazes, no entanto,
de especificar construcoes aninhadas, que sao centrais para as linguagens de programacao.
Considere a expressao de exemplo para estrutura de uma expressao aritmetica:

expr id|number| expr|(expr)|expr op expr


op +|||/
expr

expr

expr

op

expr

id

id

op

expr

id

expr

expr

op

id

expr

expr

op

expr

id

id

Correcao para a gramatica de expressao aritmetica:

expr term | expr addop term


term f actor | term multop f actor
f actor id | number| f actor | ( expr )
addop + |
multop | /

17

expr

2.2

expr

addop

term

term

term multopf actor

f actor

f actor

number(3)

number(4)

number(5)

Vinculac
oes

Existem:
As variaveis que tem sua alocacao antes que se inicie a execucao dos blocos de
comando dos programas: globais e locais;
As variaveis que sao criadas em tempo de execucao: heap;
As variaveis que existem mesmo depois da execucao: persistente.
A vinculacao e uma associacao entre duas coisas, como um nome e a coisa que com
p nome. Tempo de vinculacao e o momento em que e criada uma vinculacao ou, mais
geralmente, o momento em que e feita qualquer decisao de implementacao (podemos
pensar nisso como ligar a uma resposta a uma pergunta). Mais sucintamente:
Uma caracterstica importante das variaveis esta relacionada a ativacao da variavel
ao longo da execucao do programa.
Quando os valores das variaveis podem ser acessados.
Uma variavel existe desde o momento em que e associada a uma celula de memoria (alocacao) e termina a sua existencia quando o dado espaco e disponibilizado
(deslocacao)
Existem diferentes tempos em que as decisoes de vinculacao podem ser tomadas:
Tempo de projeto da linguagem: Na maioria das linguagens, as construcoes de
controle de fluxo, o conjunto de tipos fundamentais (primitivos), os construtores
disponveis para a criacao de tipos complexos, e muitos outros aspectos da semantica da linguagem sao escolhidos quando linguagem e projetada.

18

Tempo de implementacao da linguagem: A maioria dos manuais das linguagens


deixam uma variedade de questoes a criterio do implementador da linguagem. Exemplos incluem a precisao (n
umero de bits) dos tipos fundamentais, o acoplamento
de entrada/sada para o sistema operacional, a organizacao e tamanhos maximos de
pilha, e o manuseio de excecoes de tempo de execucao, tais como overflow (estouro)
aritmetico.
Tempo de escrita de programa: programadores, e claro, escolhem algoritmos, estruturas de dados e nomes.
Tempo de compilacao: Compiladores escolhem o mapeamento de construcoes de alto
nvel para codigo de maquina, incluindo o layout dos dados estaticamente definidos
na memoria.
Tempo de ligacao: Uma vez que a maioria dos compiladores suportar diferentes
modulos de compilacao separados de um programa em momentos diferentes e dependem da disponibilidade de uma biblioteca de sub-rotinas padrao, um programa
nao e geralmente concludo quando os varios modulos sao unidos entre si por um
ligador.
Tempo de carregamento: refere-se ao ponto em que o sistema operacional carrega o
programa na memoria para que ele possa ser executado. Em sistemas operacionais
primitivos, a escolha de enderecos de maquina para objetos dentro do programa nao
foi finalizado ate o tempo de carregamento. A maioria dos sistemas operacionais
modernos distinguem entre enderecos fsicos e virtuais. Enderecos virtuais sao escolhidos em tempo de ligacao; enderecos fsicos mudam em tempo de execucao.
Tempo de execucao: tempo de execucao e realmente um termo muito amplo que
abrange todo o perodo desde o incio ate ao final da execucao. Vinculacoes de
valores para as variaveis ocorrem em tempo de execucao, assim como uma serie de
outras decisoes que variam de linguagem para linguagem.
Em qualquer discussao de nomes e vinculacoes, e importante distinguir entre os nomes
e os objetos aos quais se referem, e identificar diversos eventos-chave:
1. Criacao de objetos
2. Criacao de vinculacoes
3. Referencias a variaveis, sub-rotinas, tipos e assim por diante, todos os quais utilizam
vinculacoes
4. Desativacao e reativacao de vinculacoes que podem ser temporariamente inutilizadas
19

5. Destruicao de vinculacoes
6. Destruicao de objetos
Ciclo de vida dos objetos geralmente correspondem a um dos tres mecanismos de
alocacao e armazenamento principais, usados para gerenciar o espaco do objeto:
1. Objetos estaticos sao dados um endereco absoluto que e mantida ao longo da execucao do programa.
2. Objetos pilha sao alocados e desalocados como u
ltimo a entrar, primeiro a sair,
geralmente em conjunto com as chamadas de sub-rotinas e retornos.
3. Objetos Heap pode ser alocada e desalocada de maneira arbitrarias. Eles exigem
um algoritmo mais geral (e caro) de gerenciamento de armazenamento.
1
2
3
4
5
6
7
8
9
10
11

var r : Integer ;
procedure P ;
var v : Integer ;
begin
r := @v ;
end ;
begin
P;
r := 1 ;
end

Objetos alocados estaticamente sao valores que nao devem mudar durante a execucao
do programa, e sao muitas vezes alocados de maneira protegida, memoria so de leitura,
de modo que qualquer tentativa inadvertida para escrever nesses espacos ira causar uma
interrupcao de processador, permitindo que o sistema operacional anuncie um erro de
tempo de execucao. Exemplos:
Constants;
Variaveis globais;
Instrucoes de controle
Junto com as variaveis locais e constantes de tempo de elaboracao, o compilador normalmente armazena uma variedade de outras informacoes associadas com a sub-rotina,
incluindo:
Argumentos e valores de retorno. Compiladores modernos mantem essas informacoes
em registros, sempre que possvel, mas a`s vezes espaco na memoria e necessario.
20

Temporarios. Estes sao geralmente valores intermedios produzidos em calculos complexos. Mais uma vez, um bom compilador ira mante-los em registros sempre que
possvel.
Informacoes de contabilidade. Isso pode incluir os endereco de retorno de subrotinas, uma referencia ao quadro de pilha do chamador (tambem chamada de vnculo dinamico), informacoes de depuracao, e outros valores.

A heap e uma regiao de armazenamento no qual sub blocos podem ser alocados e
desalocados de maneira arbitrarias.
Heaps sao necessarios para as pecas alocada dinamicamente de estruturas de dados
ligados, e por objetos como strings totalmente gerais de caracteres, listas e conjuntos, cujo
tamanho pode mudar como resultado de uma instrucao de atribuicao ou outra operacao
de atualizacao.

Alocacao de objetos baseados em heap e sempre desencadeada por alguma operacao


especfica em um programa: instanciar um objeto, adicionar ao final de uma lista,
atribuindo um valor longo em uma string curta, e assim por diante. Desalocacao
tambem e explcita em algumas linguagens (por exemplo, C, C ++ e Pascal).
A biblioteca de tempo de execucao para uma linguagem, deve entao, fornecer um
mecanismo de coleta de lixo para identificar e recuperar objetos inacessveis. A
maioria das linguagens funcionais e scripts exigem a coleta de lixo, assim como
muitas linguagens imperativas mais recentes, incluindo Modula-3, Java e C#.
21

A regiao textual do programa ao qual a vinculacao e ativa e seu escopo.


Em uma linguagem com escopo estatico, as ligacoes entre nomes e objetos pode ser
determinado em tempo de compilacao atraves da analise do texto do programa, sem
levar em consideracao o fluxo de controle em tempo de execucao.
Normalmente, a vinculacao corrente para um determinado nome e encontrado
na declaracao correspondente cujo bloco esta envolvido um determinado ponto no
programa, embora, existem muitas variacoes neste tema basico.
1 // P l a c e i n t o s a new name b e g i n n i n g w i t h t h e l e t t e r L and c o n t i n u i n g
w i t h t h e ASCII r e p r e s e n t a t i o n o f a u n i q u e i n t e g e r . Parameter s i s
assumed t o p o i n t t o s p a c e l a r g e enough t o h o l d any such name ; f o r t h e
s h o r t i n t s used here , 7 c h a r a c t e r s s u f f i c e .
2 void l a b e l n a m e ( char s ) {
3
s t a t i c short int n ; // C g u a r a n t e e s t h a t s t a t i c l o c a l s
4
// a r e i n i t i a l i z e d t o z e r o
5
s p r i n t f ( s , L%d\0 , ++n ) ; // p r i n t f o r m a t t e d o u t p u t t o s
6 }

22

1
2
3
4
5
6
7

const N = 1 0 ;
...
procedure f o o ;
const
M = N; // s t a t i c s e m a n t i c e r r o r !
...
N = 2 0 ; // l o c a l c o n s t a n t d e c l a r a t i o n ; h i d e s t h e o u t e r N

1
2
3
4
5
6
7
8

const N = 1 0 ;
...
procedure f o o ;
const
M = N; // s t a t i c s e m a n t i c e r r o r !
var
A : array [ 1 . .M] of integer ;
N : r e a l ; // h i d i n g d e c l a r a t i o n

1 class A {
2
const int N = 1 0 ;
3
void f o o ( ) {
4
const int M = N; // u s e s i n n e r N b e f o r e i t i s d e c l a r e d
5
const int N = 2 0 ;
1
2
3
4
5
6
7
8
9
10

struct manager ; // d e c l a r a t i o n o n l y
struct employee {
struct manager b o s s ;
struct employee n e x t e m p l o y e e ;
...
};
struct manager { // d e f i n i t i o n
struct employee f i r s t e m p l o y e e ;
...
};

1
2
3
4
5
6
7
8
9
10
11
12

void l i s t t a i l ( f o l l o w s e t f s ) ; // d e c l a r a t i o n o n l y
void l i s t ( f o l l o w s e t f s )
{
switch ( i n p u t t o k e n ) {
case i d : match ( i d ) ; l i s t t a i l ( f s ) ;
...
}
void l i s t t a i l ( f o l l o w s e t f s ) // d e f i n i t i o n
{
switch ( i n p u t t o k e n ) {
case comma : match (comma) ; l i s t ( f s ) ;
...

23

13 }

Em uma linguagem com escopo dinamico, as vinculacoes entre nomes e objetos


dependem do fluxo de controle em tempo de execucao, e em particular sobre a
ordem em que as sub-rotinas sao chamadas.
Em comparacao com as regras de escopo estatico discutidos, regras de escopo
dinamicos sao geralmente bastante simples: a vinculacao corrente para um determinado nome e o encontrado mais recentemente durante a execucao, e que ainda
nao foram destrudos, retornando seu escopo.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

n : integer // g l o b a l d e c l a r a t i o n

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

program main ;
var x : integer ;
procedure sub1 ;
var x : integer ;
begin
...x...;
end ;
// sub1
procedure sub2 ;
begin
sub1 ;
...x...;
end ;
// sub2
begin
...
end .
// main

procedure f i r s t
n := 1
procedure s e c o n d
n : integer // l o c a l d e c l a r a t i o n
f i r s t ()
n := 2
i f read integer ( ) > 0
second ( )
else
f i r s t ()
write integer ( n )

1 $var = G l o b a l ;
2 sub i n n e r {

24

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

print i n n e r :
$var \n ;
}
sub c h a n g e l o c a l {
my $var = L o c a l ;
print c h a n g e l o c a l :
$var \n ;
&i n n e r
}
sub changedynamic {
l o c a l $var = Dynamic ;
print changedynamic : $var \n ;
&i n n e r
}
&i n n e r
&c h a n g e l o c a l
&changedynamic

25

3 Nomes, escopos e vincula


c
oes
3.1

Significado dos nomes dentro de um escopo

Ate agora, em nossa discussao sobre a nomenclatura e escopos assumimos que ha


um mapeamento um-para-um entre nomes e objetos visveis em qualquer ponto em
um programa.
Isso nao precisa ser o caso.
Dois ou mais nomes que se referem ao mesmo objeto no mesmo ponto no programa
sao aliases.
A nome que pode referir-se a mais de um objeto num dado ponto no programa e
dito para ser sobrecarregado.
Exemplos simples de aliases ocorrem nos blocos common e declaracoes equivalence de Fortran, e nos registros variant e union de linguagens como Pascal e
C.
Eles tambem surgem naturalmente em programas que fazem uso de estruturas de
dados baseadas em ponteiro.
Uma maneira mais sutil para criar aliases em muitas linguagens e passar uma variavel por referencia a uma sub-rotina que tambem acessa essa variavel diretamente.
Considere o seguinte codigo em C++.
1
2
3
4
5
6
7
8
9

double sum , s u m o f s q u a r e s ;
...
void accumulate ( double& x ) // x i s p a s s e d by r e f e r e n c e
{
sum += x ;
s u m o f s q u a r e s += x x ;
}
...
accumulate ( sum ) ;

Listing 3.1: C++

Se sum e passado como um argumento para accumulate, em seguida, sum e x sera


aliases para o mesmo objeto, e o programa provavelmente nao vai fazer o que o
programador pretendia.
26

Este tipo de erro foi uma das principais motivacoes para fazer sub-rotinas com
escopo fechado.
A sobrecarga e o uso de um mesmo identificador para operacoes diferentes, ou seja,
um mesmo identificador pode denotar comportamento distintos.
Sobrecarga independente de contexto: a abstracao a ser aplicada depende
dos tipos dos argumentos, os quais devem corresponder aos parametros da
abstracao.
1
2
3
4
5
6

int a , b ;
float x ;
...
x = a / b;
...
x = ( float ) a / ( float ) b ;

Listing 3.2: C

Sobrecarga dependente de contexto: a abstracao a ser aplicada depende nao so


do tipo dos operandos a serem aplicados, mas tambem do tipo de resultado esperado
na expressao aonde esta sendo aplicado.
1
2
3
4
5
6
7
8
9
10

function / (m, n : integer ) r e t u r n f l o a t i s


begin
r e t u r n f l o a t (m) / f l o a t ( n ) ;
end ;
...
a , b , n : integer ;
x: float ;
...
x = a / b;
n = a / b;

Listing 3.3: Pascal

Sobrecarga independente de contexto:


<2-> Definicao: suponha duas funcoes, f1 : S1 T 1 e f2 : S2 T 2. Para
linguagens que permitem apenas sobrecarga de operadores independentes de
contexto, estas duas funcoes podem usar um mesmo identificador I se S1 e S2
sao tipos distintos. Assim, a aplicacao I(E) usara a funcao f1 se E for do tipo
S1, e f2 se E for do tipo S2.
Sobrecarga dependente de contexto:

27

<3-> Definicao: suponha duas funcoes, f1 : S1 T 1 e f2 : S2 T 2.


Para linguagens que permitem apenas sobrecarga de operadores dependentes
de contexto, estas duas funcoes podem usar um mesmo identificador I se S1 e
S2 sao tipos distintos ou T 1 e T 2 sao distintos.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

struct complex {
double r e a l , i m a g i n a r y ;
};
enum b a s e { dec , bin , oct , hex } ;
int i ;
complex x ;
void print num ( int n ) { . . .
void print num ( int n , b a s e b ) { . . .
void print num ( complex c ) { . . .
print num ( i ) ; // u s e s t h e f i r s t f u n c t i o n a bo v e
print num ( i , hex ) ; // u s e s t h e second f u n c t i o n a bo ve
print num ( x ) ; // u s e s t h e t h i r d f u n c t i o n a bo ve

Listing 3.4: C++

Monomorfismo:
<2-> Uma linguagem tem um sistema de tipos monomorficos quando cada elemento
declarado na linguagem possui um u
nico tipo.
<3-> A maioria das linguagens imperativas possui um sistema de tipos monomorficos.
Polimorfismo:
<4-> Em um sistema de tipos polimorficos, as abstracoes operam de maneira uniforme sobre argumentos de famlias de tipos relacionados.
<5-> O polimorfismo pode ser aplicado a diferentes tipos de abstracoes das linguagens. Podemos ter o polimorfismo sobre abstracoes de processos (funcoes genericas),
polimorfismo sobre tipos (tipos parametrizados ou politipos).
Monomorfismo:
1 type s t a c k i n t = i n t n o d e ;
2 i n t n o d e = record
3
elem : integer ;
4
next : s t a c k i n t ;
5
end ;

28

6
7 type s t a c k c h a r = charnode ;
8 charnode = record
9
elem : char ;
10
next : s t a c k c h a r ;
11
end ;

Listing 3.5: Pascal

Polimorfismo:
1 set of r e a l
2 array [ 0 . . 5 ] of Integer

Listing 3.6: Pascal


1 template <typename Type>
2 Type max( Type a , Type b ) {
3
return a > b ? a : b ;
4 }

Listing 3.7: C++

Polimorfismo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14

generic
type T i s private ;
with function <( x , y : T) return Boolean ;
function min ( x , y : T) return T ;
function min ( x , y : T) return T i s
begin
i f x < y then return x ;
e l s e return y ;
end i f ;
end min ;
function s t r i n g m i n i s new min ( s t r i n g , <) ;
function date min i s new min ( date , d a t e p r e c e d e s ) ;

Listing 3.8: Ada

3.2

A vinculac
ao do ambiente de referenciamento

Uma questao adicional que ainda nao tenha considerado surge em linguagens que
permitem criar uma referencia a uma sub-rotina, por exemplo, passando-a como um
parametro.

29

Quando alguma regra de escopo deveria ser aplicada a essa tal sub-rotina: quando
a referencia e criado pela primeira vez, ou quando a rotina e finalmente chamada?
A resposta e particularmente importante para as linguagens com escopo dinamico,
embora veremos que importa mesmo em linguagens com escopo estatico.
Esta ligacao tardia do ambiente de referenciamento de uma sub-rotina que foi passado como um parametro e conhecido como vinculac
ao superficial (shallow).
geralmente o padrao em linguagens com escopo dinamico.
E
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

type p e r s o n = record
. . .
age : integer
. . .
t h r e s h o l d : integer
people : database
function o l d e r than t h r e s h o l d ( p : p e r s o n ) : boolean
r e t u r n p . age >= t h r e s h o l d
procedure p r i n t p e r s o n ( p : p e r s o n )
// C a l l a p p r o p r i a t e I /O r o u t i n e s to p r i n t record on s t a n d a r d output .
// Make u s e of n o n l o c a l v a r i a b l e l i n e l e n g t h to format data in
columns .
. . .
procedure p r i n t s e l e c t e d r e c o r d s ( db : d a t a b a s e ; p r e d i c a t e , p r i n t r o u t i n e
: procedure )
l i n e l e n g t h : integer
i f d e v i c e type ( s t d o u t ) = t e r m i n a l
l i n e l e n g t h := 80
e l s e // Standard output i s a f i l e or p r i n t e r .
l i n e l e n g t h := 132
f o r e a c h record r in db
// I t e r a t i n g o v e r t h e s e may a c t u a l l y be
// a l o t more c o m p l i c a t e d than a f o r l o o p .
if predicate ( r )
print routine ( r )
// main program
. . .
t h r e s h o l d := 35
p r i n t s e l e c t e d r e c o r d s ( p e o p l e , o l d e r than t h r e s h o l d , p r i n t p e r s o n )

Vinculac
ao profunda (deep) e implementada atraves da criacao de uma representacao explcita de um ambiente de referencia (geralmente aquela em que a
sub-rotina porderia executar se chamada no momento presente) e juntamente com
uma referencia para a sub-rotina.
30

Todas essas informacoes sao chamadas como um fecho (closures). Normalmente,


a propria sub-rotina pode ser representada no fechamento por um ponteiro para o
respectivo codigo.
Em uma linguagem com escopo dinamico, a representacao do ambiente de referenciamento depende se a implementacao da linguagem usa uma lista de associacao ou
uma tabela de referencia central para pesquisa de tempo de execucao dos nomes.
Em geral, um valor em uma linguagem de programacao e dito ter status de primeira
classe se ele pode ser passado como um parametro, voltar de uma sub-rotina, ou
atribudo a uma variavel.
Tipos simples, como n
umeros inteiros e os chars sao valores de primeira classe na
maioria das linguagens de programacao.
Em contraste, um valor de segunda classe pode ser passado como um parametro,
mas nao pode voltar de uma sub-rotina ou atribudo em uma variavel,
E um valor de terceira classe nem sequer pode ser passado como um parametro.
Sub-rotinas sao valores de primeira classe em todas as linguagens de programacao
funcional e a maioria das linguagens de script.
Sub-rotinas tambem sao valores de primeira classe em C# e, com algumas restricoes,
em varias outras linguagens imperativas, incluindo Fortran, Module-2 e -3, Ada 95,
C e C++.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

program b i n d i n g e x a m p l e ( input , output ) ;


procedure A( I : integer ; procedure P) ;
procedure B ;
begin
writeln ( I ) ;
end ;
begin // A
i f I > 1 then
P
else
A( 2 , B) ;
end ;
procedure C ; begin end ;
begin // main
A( 1 , C) ;

31

19 end .

Listing 3.9: Pascal

A ambiente de referenciamento em um fecho sera nao-trivial apenas quando passar


uma sub-rotina aninhada.
Isto significa que a implementacao de sub-rotinas de primeira classe e nao-trivial em
uma linguagem sem sub-rotinas aninhadas.
Ao mesmo tempo, isso significa que um programador trabalhando em tal linguagem
estara faltando um recurso u
til: a capacidade de passar a uma sub-rotina com o
contexto.
Em linguagens orientadas a objetos, nao ha uma forma alternativa para conseguir
um efeito similar: podemos encapsular a nossa sub-rotina como um metodo de um
objeto simples, e deixamos os campos do objeto segurar o contexto para o metodo.
1
2
3
4
5
6
7
8
9
10
11

i n t e r f a c e IntFunc {
public int c a l l ( int i ) ;
}
c l a s s PlusX implements IntFunc {
f i n a l int x ;
PlusX ( int n ) { x = n ; }
public int c a l l ( int i ) { return i + x ; }
}
...
IntFunc f = new PlusX ( 2 ) ;
System . out . p r i n t l n ( f . c a l l ( 3 ) ) ; // p r i n t s 5

Listing 3.10: Java

32

3.3

Expans
ao de macros

Antes do desenvolvimento de linguagens de programacao de alto nvel, os programadores de linguagem de montagem poderiam encontrar-se escrevendo codigo altamente repetitivo.
Para aliviar a carga, muitas montadores forneceram sofisticadas implementacoes de
expans
ao de macros.
Considere a tarefa de carregar um elemento de uma matriz bidimensional da memoria
em um registrador. Esta operacao pode facilmente exigir uma meia d
uzia de instrucoes, com detalhes dependendo do conjunto de instrucoes de hardware; o tamanho
dos elementos da matriz; e se os ndices sao constantes, valores na memoria, ou valores em registradores.
Em muitos montadores pode-se definir uma macro que ira substituir uma expressao
com a sequencia de multi-instrucao apropriada, como
ld2d (reg_alvo, nome_de_matriz, linha, coluna,
tamanho_de_linha, tamanho_do_elemento)
Em um programa numerico contendo centenas ou milhares de operacoes de acesso
de matriz, esta macro pode revelar-se extremamente u
til.
1
2
3
4
5

#define
#define
// t r u e
#define
#define

LINE LEN 80
DIVIDES ( a , n ) ( ! ( ( n ) % ( a ) ) )
i f f n has z e r o remainder modulo a /
SWAP( a , b ) { int t = ( a ) ; ( a ) = ( b ) ; ( b ) = t ; }
MAX( a , b ) ( ( a ) > ( b ) ? ( a ) : ( b ) )

Listing 3.11: C

3.4

Compilac
ao separada

Como a maioria dos programas grandes sao construdos e testados de forma incremental,
e uma vez que a compilacao de um grande programa pode ser uma operacao de
muitas horas,
qualquer linguagem projetada para suportar grandes programas devem ter suporte
para a compilacao separada.

33

4 An
alise sem
antica
4.1

Regras de an
alise sem
antica

<1-> Muitos compiladores que geram codigo por verificacoes dinamicas fornecem a
opcao de desativa-los se desejar.
costume em algumas organizacoes permitir verificacoes dinamicas durante
<2-> E
o desenvolvimento e teste do programa, e em seguida desativa-los para uso em
producao, para aumentar a velocidade de execucao.
<3-> Os erros podem ser menos provaveis em producao do que em teste, mas as
consequencias de onde um erro e detectado sao significativamente piores.
Os programadores podem escrevem afirmacoes logicas sobre os valores de dados do
programa.
Algumas linguagens de programacao fazem estas afirmacoes uma parte da sintaxe
da linguagem.
O compilador gera codigo para verificar as afirmacoes em tempo de execucao.
Uma afirmacao e uma declaracao de que uma condicao especificada e esperada ser
verdade quando a execucao atinge um certo ponto no codigo. Em Java pode-se
escrever
1 a s s e r t denominator != 0 ;

Listing 4.1: Java

Por exemplo, em C:
1 a s s e r t ( denominator != 0 ) ;

Listing 4.2: C

Afirmacoes, e claro, poderiam ser usadas para cobrir os outros tipos de controles,
mas nao de forma tao clara ou sucinta.
Invariantes, pre-condicoes e pos-condicoes sao uma parte proeminente do cabecalho
do codigo a que se aplicam, e pode cobrir um n
umero potencialmente grande de
lugares onde uma afirmacao seria exibida de outra forma.

34

Em geral, os algoritmos de tempo de compilacao que predizem o comportamento de


tempo de execucao sao conhecidos como an
alise est
atica.
<3-> Tal analise e dito para ser mais precisa, se permite que o compilador determine
se um programa sempre seguira as regras.
<4-> Verificacao de tipo, por exemplo, e estatica e precisa em linguagens como Ada
e ML: o compilador garante que nenhuma variavel nunca vai ser usado em tempo
de execucao de uma forma que nao e apropriado para o seu tipo.
<2-> Por outro lado, linguagens como Lisp e Smalltalk obtem maior flexibilidade
ao aceitar a sobrecarga de tempo de execucao dos controles de tipo dinamico.
<3-> A analise estatica tambem pode ser u
teis quando nao e precisa (exata).
<4-> Compiladores, muitas vezes, verificam o que eles podem em tempo de compilacao e, em seguida, geram o codigo para verificar o resto de forma dinamica.
<5-> Exemplos incluem a analise de alias, que determina quando os valores podem ser armazenadas de forma segura em registros, calculado fora de ordem, ou
acessado por threads simultaneas;
<2-> E analise subtipo, que determina quando uma variavel em uma linguagem
orientada a objeto e a garantia de ter um determinado subtipo, de modo que seus
metodos podem ser chamados sem despacho dinamico.
<3-> Uma otimizacao e dita insegura se ela pode levar a codigo incorreto em determinados programas.
<4-> Diz-se ser especulativa se geralmente melhora o desempenho, mas pode degradarlo em determinados casos.
<5-> Um compilador e dito conservador se aplicar otimizacoes somente quando ele
pode garantir que eles estarao seguros e eficazes.
<6-> Por outro lado, um compilador otimista podem fazer uso liberal de otimizacoes
especulativas.

4.2

Gram
atica de atributos

Aqui, por exemplo, e uma gramatica de expressoes aritmeticas, com precedencia e


associatividade

35

E E+T
E ET
ET
T T F
T T /F
T F
F F
F (E)
F const

E1 E2 + T
. E1.val := sum(E2 .val, T.val)
E1 E2 T
. E1 .val := dif f erence(E2 .val, T.val)
ET
. E.val := T.val
T1 T2 F
. T 1.val := product(T2 .val, F.val)
T1 T2 /F
. T 1.val := quotient(T2 .val, F.val)

36

T F
. T.val := F.val
F1 F2
. F1 .val := additive
inverse(F2 .val)
F (E)
. F.val := E.val
F const
. F.val := const.val
<1-> Funcoes semanticas deve ser escritas em alguma notacao ja existente, porque
gramaticas de atributos realmente nao especificam o significado de um programa;
<2-> Em vez disso, eles fornecem uma maneira de associar um programa com outra
coisa que, presumidamente, tem um significado.
<3-> Nem a notacao para funcoes semanticas nem os tipos de atributos (ou seja,
o domnio dos valores passados e retornados de funcoes semanticas) e intrnseca a`
Gramatica de Atributos.
<4-> Se estivessemos interessados em definir o significado de uma linguagem de
programacao de uma forma independente de maquina, nossos atributos pode ser de
domno de denotaco
es teoricas (estes sao a base da sem
antica denotacional).
<5-> Se estivessemos interessados em provar teoremas sobre o comportamento dos
programas em nossa linguagem, nossos atributos podem ser formulas logicas (esta e
a base da sem
antica axiom
atica).

37

4.3

Avaliac
ao de atributos

<1-> Uma gramatica de atributos em que todos os atributos sao sintetizados e dito
para ser S-atribuda.
<2-> Os argumentos para funcoes semanticas em uma gramatica S-atribuda sao
sempre atributos dos smbolos no lado direito da producao, e o valor de retorno e
sempre colocado em um atributo do lado esquerdo da producao.
<3-> Tokens tem muitas vezes propriedades intrnsecas (por exemplo, a representacao de caracteres de um identificador ou o valor de uma constante numerica);
<4-> Em um compilador estes sao atributos sintetizados inicializados pelo analisador sintatico.
<1-> Em geral, podemos imaginar (e de fato tem necessidade de) atributos cujos
valores sao calculados quando o smbolo esta no lado direito da producao atual.
<2->Tais atributos sao chamados de herdados.
<3-> Eles permitem que a informacao contextual flua de um smbolo de cima ou
de lado, para que as regras da producao possam ser aplicadas de formas diferentes
(ou gerar valores diferentes), dependendo do contexto.
<4-> Informacoes sao geralmente passadas smbolo por smbolo por meio de atributos herdados.
<5-> Atributos herdados da raiz da arvore de analise tambem pode ser usado para
representar o ambiente externo (caractersticas da maquina de alvo, os argumentos
de linha de comando para o compilador, etc.).
38

Considere a seguinte gramatica:


expr const expr tail
expr tail const expr tail | 
Para o seguinte exemplo, 9 - 4 - 3

<1-> Dizemos uma gramatica de atributos e bem definida se as suas regras determinam um conjunto u
nico de valores para os atributos de cada arvore de analise
possvel.
<2-> Uma gramatica de atributos e n
ao circular se nunca leva a uma arvore de
analise em que ha ciclos de atributo no fluxo grafico, isto e, se nenhum atributo, em
qualquer arvore de analise, ja depende de si mesmo.
<3-> A gramatica pode ser circular e ainda ser bem definida se os atributos garantem convergir para um valor u
nico.
<4-> Como regra geral, gramaticas de atributos tendem a ser nao circular.
<1-> Um algoritmo que decora arvore de analise sintatica, invocando as regras de
uma gramatica de atributos em uma ordem consistente com o fluxo de atributo da
arvore e chamado de um esquema de traduc
ao.
<2-> Talvez o esquema mais simples e aquele que passa repetidas vezes ao longo
de uma arvore, chamar qualquer funcao semantica cujos argumentos foram todos
definidos, e quando completar uma passada em que os valores nao mudam.
39

<3-> Tal esquema e dito ser esquecido (oblivious), no sentido de que ele nao
explora um conhecimento especial da arvore de analise ou a gramatica.
<4-> Ele ira parar somente se a gramatica esta bem definida.
<1-> Um compilador que intercala analise semantica e geracao de codigo e dito ser
um compilador de uma passagem.
<2-> Nao e possvel afirmar se intercalacao analise semantica com analise sintatica
faz um compilador mais simples ou mais complexo.
<3-> Se a geracao de codigo intermediario e intercalada com a analise, nao e preciso
construir uma arvore sintatica para todas as situacoes (a nao ser, e claro, a arvore
de sintaxe e o codigo intermediario).
<4-> Alem disso, muitas vezes e possvel escrever o codigo intermediario para um
local de sada em tempo real, em vez de acumular-lo nos atributos da raiz da arvore
de analise.
<5-> A economia de espaco resultantes foram importantes para as geracoes anteriores de computadores, que tinham muito pouca memoria principal.
Considere o seguinte operacao: (1 + 3) 2:

40

41

5 Fluxo de controle
5.1

Fluxo de controle

A ordenacao e fundamental para a maioria (mas nao todos) os modelos de computacao.


Ele determina o que deve ser feito primeiro, o segundo, e assim por diante, para realizar
alguma tarefa desejada. Podemos organizar os mecanismos da linguagem utilizados para
especificar a encomenda em oito categorias principais:
1. Sequenciamento: Declaracoes estao a ser executado (ou expressoes avaliadas) em
um determinado especificado fim-normalmente a ordem em que aparecem no texto
do programa.
2. Selecao: Dependendo de uma condicao de tempo de execucao, uma escolha deve ser
feita entre duas ou mais instrucoes ou expressoes. As construcoes de selecao mais
comuns sao, se e caso (switch) declaracoes. Seleccao tambem e por vezes referido
como a alternacao.
3. iteracao: Um fragmento de dado de codigo deve ser executado repetidamente, ou
um determinado n
umero de vezes, ou ate que uma determinada condicao de tempo
de execucao e verdade. construcoes de iteracao incluem, por / fazer, enquanto, e
repetir loops.
4. Abstracao Procedural: Uma colecao potencialmente complexo de construcoes de
controle (uma sub-rotina) e encapsulado em uma forma que permite que ela seja
tratada como uma u
nica unidade, geralmente sujeita a parametrizacao.
5. Abstracao Procedural: Uma colecao potencialmente complexo de construcoes de
controle (uma sub-rotina) e encapsulado em uma forma que permite que ela seja
tratada como uma u
nica unidade, geralmente sujeita a parametrizacao.
6. recursao: Uma expressao e definida em termos de (versoes mais simples do) em si,
seja direta ou indiretamente; o modelo computacional requer uma pilha na qual
salvar informacoes sobre as ocorrencias parcialmente avaliado da expressao. Recursividade e geralmente definida por meio de sub-rotinas de auto-referencial.
7. simultaneidade: dois ou mais fragmentos de programa devem ser executados / avaliada , ao mesmo tempo,em paralelo quer em processadores separados, ou intercaladas num u
nico processador de uma maneira que consegue o mesmo efeito.

42

8. A manipulacao de excecao e especulacoes: Um fragmento programa e executado de


forma otimista, no pressuposto de que alguma condicao esperada sera verdade. Se
esta condicao torna-se falso, ramos de execucao para um manipulador que executa no
lugar da parte restante do fragmento protegido (no caso de tratamento de excecao),
ou em vez da totalidade do fragmento protegido (no caso de especulacao). Para a
especulacao, a implementacao da linguagem deve ser capaz de desfazer, ou reverter,
quaisquer efeitos visveis do codigo protegido.
9. Nondeterminacy: A ordenacao ou a escolha entre declaracoes ou expressoes e deliberadamente deixado indeterminado, o que implica que qualquer alternativa vai levar
a resultados corretos. Alguns idiomas requerem a escolha de ser aleatoria, ou justo,
em algum sentido formal da palavra.

5.2

Avaliac
ao de express
oes

Uma expressao consiste geralmente em qualquer objeto simples (por exemplo, uma
constante literal ou uma chamada de variavel ou constante) ou um operador ou
funcao aplicado a um conjunto de operandos ou argumentos, cada um dos quais por
sua vez e uma expressao.
comum usar o termo operador para funcoes de uso especial, sintaxe simples, e
E
usar o termo operando para um argumento de um operador.
Na maioria das linguagens imperativas, chamadas de funcao consistem de um nome
de funcao seguido por um paramentro, lista de argumentos separada por vrgula,
como em
1 my func (A, B, C)

Operadores sao tipicamente mais simples, tendo apenas um ou dois argumentos, e


dispensando os parenteses e vrgula:
1 a + b
2 c

Em geral, uma linguagem pode especificar que as chamadas de funcao que empregam
prefixo, infixo, ou notacao posfixa.
Estes termos indicam, respectivamente, se o nome da funcao aparece antes, entre
ou apos seus varios argumentos:
1 p r e f i x : op a b
2 i n f i x : a op b
3 p o s t f i x : a b op

ou

op ( a , b )

43

ou

( op a b )

A maioria das linguagens fornecem um rico conjunto de operadores aritmetico e


logicos. Quando em notacao infixa, sem parenteses, estes operadores levam a ambiguidade quanto ao que e um operando de que.
Em Fortran, por exemplo, que usa ** para exponenciacao, como devemos analisar
a + b * c ** d ** e / f? Isso deve ser agrupadas como
1 ( ( ( ( a + b ) c ) d ) e ) / f

ou
1 a + ( ( ( b c ) d ) ( e / f ) )

ou
1 a + ( ( b ( c ( d e ) ) ) / f )

ou ainda alguma outra opcao? (Em Fortran, a resposta e a u


ltima das opcoes
mostradas.)
Os programadores novatos em Pascal frequentemente escrevem condicoes como
1 i f A < B and C < D then ( ouch )

A menos que A, B, C e D sao todos do tipo booleano, o que e improvavel, este codigo
ira resultar em um erro de semantico, uma vez que as regras de precedencia fazer
com que grupo como A <(B e C) < D. ( e mesmo se todos os quatro operandos
sao do tipo booleano, o resultado e quase certo que sera algo diferente do que o
programador pretendia.)
A maioria das linguagens evita este problema, dando aos operadores aritmeticos
maior precedencia do que operadores relacionais, que por sua vez tem maior precedencia que os operadores logicos.
As excecoes notaveis incluem APL e Smalltalk, em que todos os operadores sao de
igual precedencia (parenteses deve ser usado para especificar agrupamento).
Em uma linguagem puramente funcional, expressoes sao os blocos de construcao de
programas, e computacao consiste inteiramente de avaliacao de expressao.
O efeito de qualquer expressao individual sobre o calculo global e limitada ao valor
que a expressao fornece ao seu contexto atual.

44

Tabela 5.1

Fortran

Pascal

**

not

*,/
+, - (unario e
binario)

.eq., .ne., .lt., .le.,


.gt., .ge.
(comparisons)

C
++, (pos-inc.,
dec.)
++, (pre-inc.,
dec.), +, (unario), &, *
(endereco,
conte
udo), !,
(logico, not bit a
bit)
* (binario), /, %
(modulo)

*, /, div, mod,
and
+, - (unario e
binario), or

+, - (binario)
<<, >>
(deslocamento de
bit a direita e
esquerda)
<, <=, >, >=
(teste
desigualdade)
==, != (teste
igualdade)
& (and bit a bit)
(xor bit a bit)
| (or bit a bit)

<, <=, >, >=,


=, <>, IN

.not.

.and.

&& (and logico)

.or.
.eqv., .neqv.
(comparacoes
logicas)

|| (or logico)
?: (if . . . then. .
. else)
=, +=, -=, *=,
/=, %=, >>=,
<<=, &=, =,
|= (atribuicao)
, (sequencia)

45

Ada

abs (valor
absoluto), not, **

*, /, mod, rem
+, - (unario)
+, - (binario),&
(concatenacao)
=, /= , <, <=,
>, >=

and, or, xor


(operadores
logicos)

Computacoes complexas empregam recursao para gerar um n


umero potencialmente
ilimitado de valores, expressoes e contextos.
Em uma linguagem imperativa, em contraste, o calculo consiste tipicamente de uma
serie ordenada de alteracoes nos valores das variaveis na memoria.
Atribuicoes fornecem os principais meios para fazer as alteracoes. Cada atribuicao
leva um par de argumentos: um valor e uma referencia a uma variavel, em que o
valor deve ser colocado.
Em geral, uma construcao de linguagem de programacao e dito ter um efeito colateral se influencia o calculo subsequente em qualquer outra forma que nao por
devolver um valor para utilizacao no contexto atual.
Atribuicao e talvez o efeito colateral mais fundamental: enquanto a avaliacao de
um trabalho a`s vezes pode produzir um valor, o que realmente importa e o fato de
que ela muda o valor de uma variavel, influenciando assim o resultado de qualquer
calculo mais tarde em que a variavel aparece.
Muitas (embora nao todas) linguagens imperativas distincao entre expressoes, que
sempre produzem um valor, e podem ou nao podem tem efeitos colaterais, e declaracoes,
que sao executadas exclusivamente para seus efeitos colaterais, e nao retornam valor
u
til.
Dada a centralidade da atribuicao, programacao imperativa a`s vezes e descrito como
computacao por meio de efeitos colaterais.
1
2
3
4
5
6
7
8
9
10
11
12
13

int x = 1 0 , y = 2 0 ;
int fun1 ( ) {
x = x + 30;
return 1 0 ;
}
void main ( ) {
y = y + fun1 ;
...
i f ( ( x > 5 0 ) && ( ( fun1 + y ) > 7 0 ) )
...
}

Superficialmente, atribuicao parece ser uma operacao muito simples.


No entanto, existem algumas diferencas sutis, mas importantes na semantica de
atribuicao em diferentes linguagens imperativas.
46

Estas diferencas sao muitas vezes invisvel, porque eles nao afectam o comportamento de programas simples.
Considere o seguinte atribuicao em C:
1 d = a;
2 a = b + c;

Devido a` sua utilizacao no lado esquerdo da instrucoes de atribuicao, expressoes que


indicam locais sao referidos como L-valores.
As expressoes que denotam valores (possivelmente o valor armazenado num local)
sao referidos como R-valores.
Um dos principais objetivos do projeto de Algol 68 foi fazer as varias caractersticas
da linguagem o mais ortogonal possvel.
Ortogonalidade significa que as funcionalidades podem ser usados em qualquer combinacao, as combinacoes fazem sentido, e o significado de uma caracterstica e consistente, independentemente das outras caractersticas com o qual esta sendo combinado.
O nome e destinado a estabelecer uma analogia explcita a vetores ortogonais em
algebra linear: nenhum dos vetores de um conjunto ortogonal depende dos outros,
e todas sao necessarios a fim de descrever o espaco vetorial como um todo.
Porque eles dependem dos efeitos colaterais, programas imperativos devem atualizar
esta comum em muitas linguagens para declaracoes como
uma variavel. E
1 a = a + 1;

ou pior
1 b. c [3].d = b. c [3].d e ;

Para eliminar a desordem de compilacao ou o custo de tempo de execucao de calculos


de enderecos redundantes, e para evitar o problema de efeitos colaterais repetidos,
muitas linguagens, incluindo C e seus descendentes, fornecer os chamados operadores de atribuic
ao para atualizar uma variavel.
1 a += 1 ;
2 b . c [ 3 ] . d = e ;

Em varias linguagens, incluindo Clu, ML, Perl, Python e Ruby, tambem e possvel
escrever
47

1 a, b = c , d;

Enquanto nos poderamos facilmente ter escrito


1 a = c; b = d;

Alem disso, multiplas formas de atribuicao permite que as funcoes retornem tuplas,
bem como valores individuais:
1 a , b , c = foo (d , e , f ) ;

Linguagens imperativas nem sempre fornecem um meio de especificar um valor inicial


para uma variavel em sua declaracao. Existem pelo menos tres razoes, no entanto,
porque tais valores iniciais podem ser u
til:
1. A variavel estatica que e local para uma sub-rotina precisa de um valor inicial,
para fim de ser u
til.
2. Para qualquer variavel alocada estaticamente, um valor inicial que e especificado na declaracao pode ser pre-alocados em memoria global pelo compilador,
evitando o custo de atribuir um valor inicial em tempo de execucao.
3. O uso acidental de uma variavel nao inicializada e um dos erros de programacao
mais comuns. Uma das maneiras mais faceis de prevenir tais erros e dar a cada
variavel um valor quando ele e declarado.
Enquanto regras de precedencia e associatividade definem a ordem em que os operadores infixos binarios sao aplicadas dentro de uma expressao, eles nao especificam
a ordem em que sao avaliados os operandos de um determinado operador.
Por exemplo, na expressao
1 a f (b) c d

Da mesma forma, em chamada de subrotina, com m


ultiplos argumentos
1 f (a , g(b) , h( c ) )

Ha duas razoes principais pelas quais a ordem pode ser importante:


1. Efeitos colaterais: Se f (b), pode modificar d, em seguida, o valor de a
f b) c d ira depender do fato se a subtracao ou multiplicacao e realizada
em primeiro lugar. Da mesma forma, se g(b) pode modificar a e / ou c, em
seguida, os valores passados para f (a, g(b), h(c)) dependera da ordem em que
os argumentos sao avaliados.
48

2. Melhora do c
odigo: A ordem de avaliacao das subexpressoes tem um impacto tanto na alocacao de registradores e ordenamento de instrucoes. Na
expressao a b + f (c), e provavelmente desejavel chamar f antes de avaliar a b,
porque o produto, se calculado em primeiro lugar, precisaria ser salvos durante
a chamada de f , e f pode usar todos os registradores em que ele seria salvo.
Na mesma linha, considere a sequencia
1 a := B [ i ] ;
2 c := a 2 + d 3 ;

Expressoes booleanas oferecem uma oportunidade especial e importante para uma


melhora do codigo e aumento da legibilidade.
Considere a expressao (a < b) && (b < c).
Se a e maior do que b, nao ha realmente nenhuma necessiade em verificar se b e
menor que c.
Da mesma forma, na expressao (a > b) || (b > c), se a e de fato maior que b nao ha
nenhuma necessiade em verificar se b e maior do que c.
Um compilador que executa uma avaliacao de curto-circuito de expressoes booleanas
ira gerar codigo que ignora a segunda metade de ambos os calculos quando o valor
total pode ser determinada a partir da primeira metade.
1 p = my list ;
2
3 while ( p && p>key != v a l )
4
p = p>next ;

Listing 5.1: C
1 p := m y l i s t ;
2
3 while ( p <> n i l ) and ( p . key <> v a l ) do // ouch !
4
p := p . next ;

Listing 5.2: Pascal

5.3

Fluxo estruturado e n
ao estruturado

Fluxo de controle em linguagens de montagem e conseguido por meio de saltos


condicionais e incondicionais.
49

As primeiras versoes do Fortran imitou a abordagem de baixo nvel por depender


fortemente de instrucoes goto para a maioria de controle de fluxo nao procedural.
O abandono do goto era parte de uma revolucao maior em engenharia de software
conhecido como programacao estruturada.
Programacao estruturada foi a tendencia da decada de 1970, da mesma forma que
a programacao orientada a objeto era a tendencia da decada de 1990.
Programacao estruturada enfatiza o projeto top-down, modularizacao de codigo,
tipos estruturados (registros, conjuntos, ponteiros, arrays multidimensionais), variavel descritiva e nomes constantes.
Os desenvolvedores da programacao estruturada foram capazes de demonstrar que,
dentro de uma sub-rotina, quase qualquer algoritmo imperativo bem projetado pode
ser elegantemente escrito com apenas sequenciamento, selecao e iteracao.
Em vez de rotulos, linguagens estruturadas contam com os limites das construcoes
lexicamente aninhadas como as alvos em ramificacao de controle.
Devolucoes e goto (locais) permitem o controle para retornar a partir da sub-rotina
atual.
Pode fazer sentido para retornar de rotina de fora do contexto atual.
Imagine, por exemplo, que estamos `a procura de um item correspondente em algum
padrao desejado dentro de uma colecao de arquivos.
A rotina de busca pode invocar varias rotinas aninhadas, ou um u
nica rotina varias
vezes, uma vez para cada lugar em que deseja pesquisar.
1
2
3
4
5
6
7
8
9
10
11
12
13
14

function s e a r c h ( key : s t r i n g ) : s t r i n g ;
var r t n : s t r i n g ;
procedure s e a r c h f i l e ( fname : s t r i n g ) ;
begin
f o r . . . // i t e r a t e o v e r l i n e s
i f found ( key , l i n e ) then begin
r t n := l i n e ;
goto 1 0 0 ;
end ;
end ;
...
begin // s e a r c h
f o r . . . // i t e r a t e o v e r f i l e s
s e a r c h f i l e ( fname ) ;

50

15 1 0 0 : r e t u r n r t n ;
16 end ;

Listing 5.3: Pascal

No caso de um goto nao local, a implementacao da linguagem deve garantir a reparacao


da pilha de tempo de execucao de informacoes da chamada da sub-rotina. Esta operacao
de reparo e conhecido como unwinding.

5.4

Sequ
encia

Tal como a atribuicao, sequenciacao e central para programacao imperativa.


o principal meio de controlar a ordem em que ocorrem efeitos colaterais (por
E
exemplo, atribuicoes): quando uma instrucao segue outro no texto do programa, a
primeira instrucao e executada antes da segunda.
Na maioria das linguagens imperativas, as listas de declaracoes pode ser entre os
delimitadores begin...end ou {...} e entao usadas em qualquer contexto em que se
preve uma u
nica instrucao.
Tal lista delimitada e normalmente chamado de uma instrucao composta.
A instrucao composta e opcionalmente precedido por um conjunto de declaracoes,
a`s vezes chamada de bloco.

5.5

Selec
ao

Instrucoes de selecao na maioria das linguagens imperativas empregam alguma variante da notacao if...then...else introduzido em Algol 60:
1
2
3
4
5

i f c o n d i t i o n then s t a t e m e n t
e l s e i f c o n d i t i o n then s t a t e m e n t
e l s e i f c o n d i t i o n then s t a t e m e n t
...
else statement

Listing 5.4: Algol

1
2
3
4
5

IF a = b THEN . . .
ELSIF a = c THEN . . .
ELSIF a = d THEN . . .
ELSE . . .
END

51

Listing 5.5: Modula-2


1 ( cond
2
((=
3
4
((=
5
6
((=
7
8
(T
9

A B)
(...) )
A C)
(...) )
A D)
(...) )
(...) ))

Listing 5.6: Lisp

Enquanto a condicao if...then...else e uma expressao booleana, normalmente nao


ha necessidade de avaliacao dessa expressao resultar em um valor booleano em um
registrador.
A maioria das maquinas fornecem instrucoes de desvio condicional que capturam
comparacoes simples.
Dito de outra maneira, a finalidade da expressao booleana em condicao de selecao
nao e para calcular um valor a ser armazenado, mas para fazer com que o controle
ramifique para varios locais.
Esta observacao nos permite gerar codigo particularmente eficaz (chamado c
odigo
de salto) para expressoes que sao passveis de avaliacao de curto-circuito.
1 i f ( (A > B) and (C > D) ) or (E <> F) then
2
then clause
3 else
4
else clause

Listing 5.7: Pascal


1
2
3
4
5
6
7
8
9
10

r1
r2
r1
r2
r3
r2
r1
r2
r3
r2

:=
:=
:=
:=
:=
:=
:=
:=
:=
:=

A l o a d
B
r1 > r2
C
D
r2 > r3
r1 & r2
E
F
r 2 <> r 3

52

11
12
13 L1 :
14
15 L2 :
16 L3 :

r 1 := r 1 | r 2
i f r 1 = 0 goto L2
t h e n c l a u s e // l a b e l nao r e a l m e n t e usada
goto L3
else clause

Listing 5.8: Codigo intermedi


ario
1
2
3
4
5
6
7
8
9
10
11
12
13

r 1 := A
r 2 := B
i f r 1 <= r 2 goto L4
r 1 := C
r 2 := D
i f r 1 > r 2 goto L1
L4 : r 1 := E
r 2 := F
i f r 1 = r 2 goto L2
L1 : t h e n c l a u s e
goto L3
L2 : e l s e c l a u s e
L3 :

Listing 5.9: Codigo intermedi


ario
1
2
3
4
5
6
7

CASE . . .
1:
| 2 , 7:
| 3..5:
|
10:
ELSE
END

< p o t e n t i a l l y complicated expression >


clause A
clause B
clause C
clause D
clause E

Listing 5.10: Modula-2


1 switch ( . . . < t e s t e d e x p r e s s i o n >) {
2
case 1 : c l a u s e A
3
break ;
4
case 2 :
5
case 7 : c l a u s e B
6
break ;
7
case 3 :
8
case 4 :
9
case 5 : c l a u s e C
10
break ;
11
case 1 0 : c l a u s e D
12
break ;
13
default : c l a u s e E

53

OF

14
15 }

break ;

Listing 5.11: C, C++, Java

5.6

Iterac
ao

Programadores em linguagens imperativas tendem a usar mais iteracao do que usam


recursividade (recursao e mais comum em linguagens funcionais).
Na maioria das linguagens, iteracao assume a forma de lacos (loops).
Como as declaracoes em uma sequencia, as iteracoes de um loop sao geralmente
executados por seus efeitos colaterais: as suas modificacoes de variaveis.
Loops vem em duas variedades principais, que diferem nos mecanismos utilizados
para determinar quantas vezes repetir.
Um loop controlado por enumeracao e executada uma vez para cada valor em um
determinado conjunto finito; o n
umero de iteracoes e conhecido antes da primeira
iteracao comecar.
Um loop controlado logicamente e executado ate que alguma condicao booleana
(que deve geralmente depender de valores alterados no loop) altere o valor.
As duas formas de loops de compartilhar uma u
nica construcao em Algol 60 e
Common Lisp. Eles sao distintas na maioria dos outras linguagens.
1 do i = 1 , 1 0 , 2
2 ...
3 enddo

Listing 5.12: Fortran


1 FOR i := f i r s t TO l a s t BY s t e p DO
2 ...
3 END

Listing 5.13: Modula-2


1 f o r ( i = f i r s t ; i <= l a s t ; i += s t e p ) {
2 ...
3 }

Listing 5.14: C

54

1 {
2
3
4
5
6
7 }

i = first ;
while ( i <= l a s t ) {
...
i += s t e p ;
}

Listing 5.15: C
1
2
3
4
5
6

BinTree<I n t e g e r > myTree = . . .


...
f o r ( I t e r a t o r <I n t e g e r > i t = myTree . i t e r a t o r ( ) ; i t . hasNext ( ) ; ) {
I n t e g e r i = i t . next ( ) ;
System . out . p r i n t l n ( i ) ;
}

Listing 5.16: Java


1
2
3
4
5
6

b i n t r e e <int> my tree = . . .
...
f o r ( b i n t r e e <int > : : i t e r a t o r n = my tree>b e g i n ( ) ;
n != my tree>end ( ) ; ++n ) {
c o u t << n << \n ;
}

Listing 5.17: C++


1 repeat
2
readln ( l i n e )
3 until l i n e [ 1 ] = $ ;

Listing 5.18: Ada


1 do {
2
line = read line ( stdin ) ;
3 } while ( l i n e [ 0 ] != $ ) ;

Listing 5.19: C
1 o u t e r : while (>) { # i t e r a t e o v e r l i n e s o f i n p u t
2
foreach $c ( s p l i t / / ) { # i t e r a t e o v e r rema in ing c h a r s
3
l a s t o u t e r i f ( $c = $ ) ; # e x i t main l o o p i f we s e e a $ s i g n
4
consume char ( $c ) ;
5
}
6 }

Listing 5.20: Perl

55

As vezes e dito que a iteracao e mais eficiente do que a recursividade.


mais correto dizer que a implementacao ingenua da iteracao e geralmente mais
E
eficiente do que a implementacao simples de recursao.
Uma otimizacao do compilador, no entanto, especialmente em uma linguagem
funcional, muitas vezes e capaz de gerar excelente codigo para funcoes recursivas.
Sao propensos a faze-lo para as funcoes de recurs
ao de cauda.
Uma funcao de recursao de cauda e aquela em que a computacao adicional nao segue
uma chamada recursiva.
Para essas funcoes, espaco de pilha alocada dinamicamente e desnecessaria: o compilador pode reutilizar o espaco pertencente a` iteracao atual quando faz a chamada
recursiva.
1 int gcd ( int a , int b ) {
2
// assume a , b > 0
3 start :
4
i f ( a == b ) return a ;
5
else i f ( a > b) {
6
a = ab ; goto s t a r t ;
7
} else {
8
b = ba ; goto s t a r t ;
9
}
10 }

Listing 5.21: C - recurs


ao de cauda

Ao longo da discussao ate agora temos assumido implicitamente que os argumentos


sao avaliados antes de passa-los para uma sub-rotina.
possvel passar uma representacao dos argumentos
Isso nao precisa ser o caso. E
nao avaliadas para a sub-rotina em vez disso, e avalia-los apenas quando o valor e
realmente necessario.
A primeira opcao (avaliacao antes da chamada) e conhecida como a avaliacao de
ordem aplicada;
Este u
ltimo (avaliando apenas quando o valor e realmente necessario) e conhecida
como a avaliacao de ordem normal.
Avaliacao em ordem normal e o que ocorre naturalmente em macros.

56

Ocorre tambem na avaliacao booleana curto-circuito, parametros de chamada-pornome, e em certas linguagens funcionais.
A partir dos pontos de vista de clareza e eficiencia, avaliacao de ordem aplicada e
geralmente prefervel a uma avaliacao de ordem normal.
Em algumas circunstancias, no entanto, a avaliacao em ordem normal pode realmente levar a codigo mais rapido, ou para um codigo que funciona quando a avaliacao aplicada levaria a um erro de tempo de execucao.
Em ambos os casos, o que importa e que a avaliacao em ordem normal, por vezes,
nao vai avaliar um argumento como um todo, se o seu valor nunca e realmente
necessario.
Scheme preve a avaliacao em ordem normal opcionalmente na forma de funcoes
chamadas delay e force.
Estas fornecem uma implementacao da avaliac
ao preguicosa.
Na ausencia de efeitos colaterais, avaliacao preguicosa tem a mesma semantica como
a avaliacao de ordem normal, mas a implementacao mantem o controle das expressoes as quais ja foram avaliadas, para que ele possa reutilizar os seus valores se
forem necessarios mais de uma vez em um determinado ambiente de referenciamento.

5.7

N
ao determin
ancia

Nossa u
ltima categoria de fluxo de controle e nao determinancia.
Uma construcao nao determinista e aquele em que a escolha entre as alternativas
(isto e, entre os caminhos de controle) e deliberadamente nao especificado.
Ja vimos exemplos de nao determinancia na avaliacao das expressoes: na maioria
das linaguagens, argumentos de operadores ou sub-rotinas podem ser avaliadas em
qualquer ordem.
Algumas linguagens, como Algol 68 e varias linguagens similares, proporcionam
mecanismos mais amplos nao determinsticos.

57

6 Tipos de dados
6.1

Tipos de dados

Tipos de servir a dois propositos principais:


1. Tipos fornecem um contexto implcito para muitas operacoes, de modo que o programador nao tem que especificar o contexto explicitamente.
Em C, por exemplo, a expressao a + b e usada na adicao de inteiro se a e b
sao de tipo inteiro ou adicao de ponto flutuante se a e b sao do tipo double (de
ponto flutuante).
2. Tipos limitam o conjunto de operacoes que podem ser realizadas em um programa
semanticamente valido.
Eles impedem o programador da adicao de um caractere em um registro, por
exemplo, ou passar um arquivo como um parametro para uma sub-rotina que
espera um n
umero inteiro.
Embora nenhum sistema de tipo pode prometer pegar operacoes sem sentido que
um programador pode colocar em um programa por engano, bons sistemas do tipo
pegam erros suficientes para serem praticas.

6.2

Checagem de tipos

Na maioria das linguagens de tipagem estatica, cada definicao de um objeto (constante, variavel, sub-rotina, etc.) deve especificar um tipo.
Muitos dos contextos em que um objeto pode aparecer tambem sao digitados, no
sentido de que as regras da linguagem restrinjam os tipos que um objeto, nesse
contexto, pode validamente possuir.
No mnimo, o objeto pode ser utilizado se o seu tipo e o tipo esperado pelo contexto
sao equivalentes (isto e, o mesmo).
Em muitas llinguagens a compatibilidade e uma relacao mais flexvel do que a equivalencia: objetos e contextos sao frequentemente compatveis mesmo quando seus
tipos sao diferentes.

58

Logo
Conversao de tipo que altera um valor de um tipo em um de outro valor;
Coercao, que realiza uma conversao automaticamente em determinados contextos;
Tipos n
ao convertidos, que sao por vezes usados em programacao de sistemas
para interpretar os bits de um valor de um tipo como se representassem um
valor de algum outro tipo.
Em uma linguagem na qual o usuario pode definir novos tipos, existem duas principais
formas de definir o tipo de equivalencia.
Equivalencia estrutural baseia-se no teor de definicoes de tipo: a grosso modo, dois
tipos sao o mesmo se eles consistem dos mesmos componentes, juntos na mesma
equivalencia.
1
2
3
4

int i ;
f l o a t f1 , f 2 ;
...
f1 = i + f2 ;

Em uma linguagem na qual o usuario pode definir novos tipos, existem duas principais
formas de definir o tipo de equivalencia.
Equivalencia de nome baseia-se na ocorrencia lexica de definicoes de tipo: a grosso
modo, cada definicao introduz um novo tipo.
1 type i n t 1 = Integer ;
2 type i n t 2 = Integer ;
3 var v1 : i n t 1 ;
4
v2 : i n t 2 ;

Convers
ao. Dependendo dos tipos envolvidos, a conversao pode ou nao pode exigir
codigo a ser executado em tempo de execucao. Ha tres casos principais:
1. Os tipos poderiam ser considerados estruturalmente equivalente, mas a linguagem usa equivalencia de nome. Neste caso os tipos de empregar a mesma
representacao de baixo nvel, e tem o mesmo conjunto de valores. A conversao
e, portanto, uma operacao puramente conceitual; nenhum codigo tera que ser
executado em tempo de execucao.
2. Os tipos tem diferentes conjuntos de valores, mas os valores que se intersectam
sao representados da mesma maneira. Um tipo pode ser um sub-intervalo do
59

outro, por exemplo. Se o tipo fornecido tem alguns valores que o tipo esperado
nao tem, entao o codigo deve ser executado em tempo de execucao para garantir
que o valor atual esta entre aqueles que sao validos no tipo esperado.
Se a verificacao falhar, entao um erro semantico dinamico acontece.
Se a verificacao for bem sucedida, entao a representacao subjacente do
valor pode ser usado, sem alteracoes.
3. Os tipos tem diferentes representacoes de baixo nvel, mas nos ainda pode
definir algum tipo de correspondencia entre os seus valores.
Um n
umero inteiro de 32-bit, por exemplo, pode ser convertido para um
n
umero de ponto flutuante de precisao dupla IEEE sem perda de precisao.
Um n
umero de ponto flutuante pode ser convertido para um inteiro arredondando ou truncando, mas dgitos fracionarios serao perdidos.
Sempre que uma linguagem permite que um valor de um tipo a ser utilizado num
contexto que espera outro, a implementacao da linguagem de efetuar uma conversao
automatica, implcita para o tipo esperado.
Esta conversao e chamado um tipo de coercao.
A coercao pode exigir um codigo de tempo de execucao execute uma verificacao
semantica dinamica, ou converta entre representacoes de baixo nvel.
Vimos como a verificacao de tipo assegura que os componentes de uma expressao
(por exemplo, os argumentos de um operador binario) tenha os tipos apropriados.
Mas o que determina o tipo da expressao em geral?
Na maioria dos casos, a resposta e facil.
O resultado de um operador aritmetico tem geralmente o mesmo tipo que os
operandos.
O resultado de uma comparacao e geralmente booleano.
O resultado de uma chamada de funcao tem o tipo declarado no cabecalho da
funcao.
O resultado de uma atribuicao (nas lnguas em que as atribuicoes sao expressoes) tem o mesmo tipo que o lado esquerdo.
Em alguns casos, no entanto, a resposta nao e obvia. Em particular, as operacoes
em subintervalos e objetos compostos nao necessariamente preservam os tipos dos
operandos.
60

Sub-arranjos
1 type Atype = 0 . . 2 0 ;
2
Btype = 1 0 . . 2 0 ;
3
4 var a : Atype ;
5
b : Btype ;

qual e o tipo de a + b? Certamente nao e nem Atype nem Btype, uma vez que os
valores possveis variam de 10 para 40.
Pode-se imaginar que seja um novo tipo anonimo com 10 e 40 como limites. A
resposta usual em Pascal e dizer que o resultado de qualquer operacao aritmetica
em um Sub-arranjos tem o tipo base do Sub-arranjos, neste caso inteiro.
Se o resultado de uma operacao aritmetica e atribudo para uma variavel de um
tipo de sub-intervalo, em seguida, uma verificacao semantica dinamica pode ser
necessaria.
Para evitar a despesa de algumas verificacoes desnecessarias, um compilador pode
acompanhar em tempo de compilacao dos maiores e menores valores possveis de
cada expressao, em essencia calcular tipo 10 ... 40.

6.3

Layout da mem
oria

Os campos de um registro sao normalmente armazenados em locais adjacentes na


memoria.
Na sua tabela de smbolos, o compilador mantem o controle do deslocamento de
cada campo dentro de cada tipo de registro.
Quando se precisa acessar um campo, o compilador normalmente gera uma instrucao
de carregar ou armazenar com deslocamento de enderecamento.
Para um objeto local, o registo de base e o ponteiro do quadro; o deslocamento e a
soma do deslocamento para o registro a partir do ponteiro e o campo e compensado
dentro do registro.

61

Um layout provavel para um tipo de elemento em uma maquina de 32 bits aparece


na figura.
Porque o campo name e apenas dois caracteres, que ocupa dois bytes de memoria.
umero inteiro, e deve (na maioria das maquinas) estar
Ja atomic number e um n
alinhado a` palavra, ha dois bytes de buraco entre o fim de name e o incio de
atomic number.
Da mesma forma, uma vez que as variaveis booleanas (na maioria das implementacoes de linguagens) ocupam um u
nico byte, existem tres bytes de espaco vazio
entre o final do campo metallic e a proxima localizacao na sequencia.
Em uma serie de elementos, a maioria dos compiladores iria dedicar 20 bytes para
cada membro da matriz.
Algumas linguagens permitem que o programador especifique que um tipo registro
(ou um conjunto) deve ser empacotado:
1 type e l e m e n t = packed record
2
name : t w o c h a r s ;
3
atomic number : integer ;
4
atomic weight : real ;
5
m e t a l l i c : Boolean
6
end ;

62

6.4

Dimens
oes, limites e Aloca
c
ao

Para matrizes estaticas, o armazenamento pode ser gerida da forma usual:


alocacao estatica para matrizes cujo tempo de vida e todo o programa;
pilha de alocacao para matrizes cujo tempo de vida e uma chamada de uma
sub-rotina;
alocacao de pilha para matrizes alocadas dinamicamente com vida u
til mais
geral.
Gerenciamento de armazenamento e mais complexa para matrizes cuja dimensao
nao e conhecida ate o tempo de elaboracao, ou cuja forma pode mudar durante a
execucao.
Para estes, o compilador deve providenciar nao so o espaco, mas tambem para ter
a informacao da forma em tempo de execucao (sem tais informacoes, a indexacao
nao seria possvel).
Algumas linguagens de tipagem dinamica permitem em tempo de execucao de ligacao de ambos o n
umero e limites de dimensoes.
Linguagens compiladas podem permitir que os limites sejam dinamicos, mas precisam que o n
umero de dimensoes seja estatico.
Um array local cuja forma e conhecida em tempo de elaboracao pode ainda ser
alocado na pilha.
Uma matriz cujo tamanho pode mudar durante a execucao deve, geralmente, ser
alocado no heap.

63

Informaco
es de dimens
oes
Informaco
es de contabilidade (campos comprimento e cabecalho para manter
o controle dos blocos alocados)
1 f o r ( i = 0 ; i < N; i ++) { // rows /
2
f o r ( j = 0 ; j < N; j ++) { // columns /
3
. . . A[ i ] [ j ] . . .
4
}
5 }
1 do j = 1 , N ! columns
2
do i = 1 , N ! rows
3
. . . A( i , j ) . . .
4
end do
5 end do

64

1
2
3
4
5
6
7

char days [ ] [ 1 0 ] = {
Sunday , Monday , Tuesday ,
Wednesday , Thursday ,
Frida y , Saturday
};
...
days [ 2 ] [ 3 ] == s ; / i n Tuesday /

1
2
3
4
5
6
7

char days [ ] = {
Sunday , Monday , Tuesday ,
Wednesday , Thursday ,
Frida y , Saturday
};
...
days [ 2 ] [ 3 ] == s ; / i n Tuesday /

65

66

7 Abstra
c
oes: Subtorinas e Controle
da Abstra
c
ao
7.1

Sequ
encia de chamadas

Em languagem com subrotinas aninhadas, pelo menos, parte do trabalho necessario


para manter o cadeia deve ser estatico e realizada pelo chamador, em vez do receptor,
porque este trabalho depende da profundidade lexica do chamador.
A abordagem padrao e para o chamador para calcular a vinculacao estatico do
receptor e passa-lo como um parametro extra, escondido. Dois subcasos surgem:
1. O receptor esta aninhado (diretamente) no interior do chamador. Neste caso,
vnculo estatico do receptor deve referir-se ao quadro do chamador. Por conseguinte, o chamador passa o seu proprio ponteiro de quadro como vnculo
estatico do receptor.
2. O receptor e k 0 escopos para fora proximo ao nvel exterior lexico. Neste
caso, todos os escopos que rodeiam o receptor tambem rodeiam o chamador
(caso contrario, o receptor nao seria visvel). O chamador dereferences seus

67

proprias ligacoes estatica k vezes e passa o resultado como vnculo estatico do


receptor.
Manutencao da pilha de chamadas de sub-rotina e da responsabilidade da sequencia
do chamadas
codigo executado pelo chamador imediatamente antes e depois de uma subrotina chamada;
e do pr
ologo (codigo executado no incio) e do eplogo (codigo executado no final)
do sub-rotina em si.
Algumas vezes o termo sequencia de chamada e utilizado para referir as operacoes
combinadas do chamador, o prologo e o eplogo.
Para manter este layout de pilha, a sequencia de chamadas pode operar como segue.
O chamador
1. <3-> salva qualquer registro do chamador cujos valores serao necessarios apos
a chamada
2. <4-> calcula os valores de argumentos e move-os para a pilha ou registros
3. <5-> calcula o vnculo estatico (se esta e uma linguagem com sub-rotinas
aninhadas), e passa-o como um argumento extra, escondido
4. <6-> usa uma instrucao de chamada de uma sub-rotina especial para ir para a
sub-rotina, passando, simultaneamente, o endereco de retorno na pilha ou num
registro
Pr
ologo o receptor
1. <2-> aloca um quadro subtraindo uma constante apropriada de sp
2. <3-> salva o ponteiro do quadro velho na pilha, e atribui a ele um novo valor
apropriado
3. <4-> salva qualquer registro do receptor que podem ser substitudos pela rotina
atual (Incluindo o vnculo estatico e endereco de retorno, se eles foram passados
em registros)
Apos a sub-rotina for concluda, o eplogo
1. <2-> move o valor de retorno para um registro ou uma localizacao reservada
na pilha
2. <3-> restaura registros do receptor, se necessario
68

3. <4-> restaura o fp eo sp
4. <5-> salta de volta para o endereco de retorno
Finalmente, o chamador
1. <6-> move o valor de retorno para onde for necessario
2. <7-> restaura registros do chamador, se necessario

7.2

Passagem de par
ametros

A maioria das sub-rotinas sao parametrizadas: elas tomam argumentos que controlam certos aspectos de seu comportamento, ou especificam os dados em que elas
estao operarando.
Os nomes dos parametros que aparecem na declaracao de uma sub-rotina sao conhecidos como par
ametros formais.
Variaveis e expressoes que sao passados para uma sub-rotina em uma chamada
particular, sao conhecidos como par
ametros atuais.
Temos sido referindo-se a parametros atuais como argumentos.
1 p(x) ;

69

Do ponto de vista da implementacao, temos duas alternativas principais:


1. poderemos executar p com uma copia do valor de x,
2. ou podemos executa-lo com o endereco de x.
Os dois modos de passagem de parametros mais comuns, chamadas por valor e
chamada por refer
encia, sao projetados para refletir essas implementacoes.
Se o proposito da chamada-por-referencia e a permitir o chamado de rotina para
modificar o parametro real, podemos conseguir um efeito similar utilizando a chamada
por valor / resultado, um modo introduzido em Algol W.
Como chamada por valor, chamar por valor / resultar copia o parametro real para
o parametro formal no incio da execucao sub-rotina.
Ao contrario de chamada por valor, ele tambem copia o parametro formal de volta
para o parametro real quando a sub-rotina retorna.
Chamada por valor e chamar por referencia fazem mais sentido em uma linguagem
com um modelo de valor de variaveis: eles determinam se copiamos a variavel ou
passamos um alias para ela.
Nenhuma opcao realmente faz sentido em uma linguagem como Smalltalk, Lisp,
ML, ou Clu, em que uma variavel ja e uma referencia.
Aqui e mais natural simplesmente para passar a propria referencia, e deixar os
parametros atuais e formais se referem ao mesmo objeto.
Clu chama de chamada-compartilhada este modo.
diferente de chamada por valor, porque, embora fazemos uma copia do parametro
E
atual para o parametro formal, ambos sao referencias;
Se modificarmos o objeto ao qual o parametro formal refere-se, o programa sera
capaz de ver essas alteracoes atraves do parametro atual apos a sub-rotina retornar.
Chamada por compartilhada tambem e diferente da chamada por referencia, porque,
embora a rotina chamada possa alterar o valor do objeto ao qual o parametro atual
refere-se, nao pode alterar a identidade do objeto.
Em uma linguagem que fornece parametros de referencia e valor (por exemplo, Pascal ou Modula), ha duas razoes principais pelas quais o programador pode escolher
um sobre o outro.

70

1. Em primeiro lugar, se a rotina de chamada pode alterar o valor de um parametro


atual (argumento), em seguida, o programador deve transmitir o parametro de
referencia. Por outro lado, para garantir que a rotina de chamada nao pode
modificar o argumento, o programador pode transmitir o parametro de valor.
2. Em segundo lugar, a implementacao de parametros de valor exige dados efetivos
de copia, uma operacao potencialmente demoradoa quando argumentos sao
grandes, parametros de referencia pode ser aplicada de forma simples por meio
de um endereco.
Para combinar a eficiencia dos parametros de referencia e a seguranca dos parametros de valor, Modula-3 oferece um modo de parametros READONLY.
Qualquer parametro formal cuja declaracao e precedida por READONLY nao pode
ser alterado pela chamada de rotina: o compilador impede o programador de usar
esse parametro formal sobre o lado esquerdo de qualquer instrucao de atribuicao,
le-lo a partir de um arquivo ou de passa-lo por referencia a qualquer outra sub-rotina.
Parametros READONLY pequenos sao geralmente implementadas, passando um
valor;
Parametros READONLY maiores sao implementadas por meio de um endereco.
Como em Fortran, um compilador Modula-3 ira criar uma variavel temporaria
para armazenar o valor de qualquer expressao construda passado como um grande
parametro READONLY.
O equivalente de parametros READONLY tambem esta disponvel em C
1 void a p p e n d t o l o g ( const h u g e r e c o r d r ) { . . .
2 ...
3 a p p e n d t o l o g (& my record ) ;

Os programadores que mudar para C, apos alguma experiencia com Pascal, Modula,
ou Ada sao muitas vezes frustrados pela falta de parametros de referencia do C.
Pode-se modificar um objeto passando o seu endereco, mas, em seguida, o parametro
formal e um ponteiro, e deve ser desreferenciado explicitamente sempre que e usado.
C++ resolve este problema atraves da introducao de uma nocao explcita de uma
refer
encia.
Parametros de referencia sao especificadas precedendo o seu nome com um e comercial no cabecalho da funcao:

71

1 void swap ( int &a , int &b ) { int t = a ; a = b ; b = t ; }

Um fechamennto (uma referencia a uma sub-rotina, em conjunto com o seu ambiente


fazendo referencia) podem ser transmitidos como um parametro para qualquer uma
de varias razoes.
A mais obvia delas surge quando o parametro e declarado para ser uma sub-rotina
(`as vezes chamado de sub-rotina formal).
1 procedure a p p l y t o A ( function f ( n : integer ) : integer ;
2
var A : array [ low . . h i g h : integer ] of integer ) ;
3
var i : integer ;
4 begin
5
f o r i := low to h i g h do A[ i ] := f (A[ i ] ) ;
6 end ;
7
...
8
var k : integer ; // in n e s t e d s c o p e
9
...
10
function add k (m : integer ) : integer ;
11
begin
12
add k := m + k ;
13
end ;
14
...
15
k := 3 ;
16
a p p l y t o A ( add k , my array ) ;

7.3

Subrotinas e m
odulos

Sub-rotinas fornecer um caminho natural para executar uma operacao para uma
variedade de valores diferentes de objetos (parametros).
Em grandes programas, a necessidade tambem surge muitas vezes para realizar uma
operacao para uma variedade de diferentes tipos.
Um sistema operacional, por exemplo, tende a fazer uso pesado de filas, para manter processos, descritores de memoria, buffers de arquivo, blocos de controle do
dispositivo, e uma serie de outros objetos.
As caractersticas da estrutura de dados de fila sao independentes das caractersticas
dos itens colocados na fila.

72

Em uma linguagem como Pascal ou Fortran, essa declaracao estatica do tipo de item
significa que o programador deve criar copias separadas da fila para cada tipo de
item, mesmo que todo o texto dessas copias (exceto os nomes de tipo nos cabecalhos
de procedimento ) e o mesmo.
Em algumas linguagens (C e um exemplo obvio) e possvel definir uma fila de ponteiros para objetos arbitrarios, mas o uso de uma tal fila requer troca do tipo e
abandonar a verificacao em tempo de compilacao.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

generic
type item i s private ;
max items : in i n t e g e r := 1 0 0 ;
package queue i s
procedure enqueue ( i t : in item ) ;
function dequeue return item ;
private
subtype i n d e x i s i n t e g e r range 1 . . max items ;
i t e m s : array ( i n d e x ) of item ;
n e x t f r e e , n e x t f u l l : i n d e x := 1 ;
end queue ;
package body queue i s
procedure enqueue ( i t : in item ) i s begin
i t e m s ( n e x t f r e e ) := i t ;
n e x t f r e e := n e x t f r e e mod max items + 1 ; end enqueue ;
function dequeue return item i s r t n : item := i t e m s ( n e x t f u l l ) ;
begin
n e x t f u l l := n e x t f u l l mod max items + 1 ;
return r t n ; end dequeue ;
end queue ;
...
package r e a d y l i s t i s new queue ( p r o c e s s ) ;
package i n t q u e u e i s new queue ( i n t e g e r , 5 0 ) ;

1 template<c l a s s item , int max items = 100>


2 c l a s s queue {
3
item i t e m s [ max items ] ;
4
int n e x t f r e e ;
5
int n e x t f u l l ;
6 public :
7
queue ( ) { n e x t f r e e = n e x t f u l l = 0 ; }
8
void enqueue ( item i t ) {
9
items [ n e x t f r e e ] = i t ;
10
n e x t f r e e = ( n e x t f r e e + 1 ) % max items ;
11
item dequeue ( ) {
12
item r t n = i t e m s [ n e x t f u l l ] ;
13
n e x t f u l l = ( n e x t f u l l + 1 ) % max items ;

73

14
15
16
17
18

return r t n ; }
};
...
queue<p r o c e s s > r e a d y l i s t ;
queue<int , 50> i n t q u e u e ;

Listing 7.1: C++

7.4

Manipulac
ao de exce
c
ao

A manipulacao de excecao geralmente requer a implementacao da linguagem para


relaxar a pilha de chamadas de sub-rotina.
Uma excecao pode ser definida como uma condicao incomum inesperada, que surge
durante a execucao do programa, e que nao podem ser facilmente tratadas no contexto local.
Podem ser detectados automaticamente pela implementacao da linguagem, ou o
programa pode evidencia-la explicitamente.
As excecoes mais comuns sao varios tipos de erros em tempo de execucao. Em uma
biblioteca de I / O, por exemplo, uma rotina de entrada pode encontrar o fim do
seu arquivo antes de ele pode ler um valor solicitado, ou pode encontrar sinais de
pontuacao ou letras na entrada quando se esta esperando dgitos.
Para lidar com esses erros sem um mecanismo de tratamento de excecao, o programador tem basicamente tres opcoes, nenhuma das quais e inteiramente satisfatoria:
1. Invente um valor que pode ser usado pelo chamador quando um valor real
nao pode ser devolvido.
2. Retorne um valor de status para o chamador, que deve inspeciona-lo depois
de cada chamada. O status pode ser escrita em um parametro extra, explcito,
armazenado em uma variavel global, ou codificados como padroes de bits de
outro modo invalidos de valor de retorno normal de uma funcao.
3. O chamador passa um fechamento (em linguagens que dao suporte) para uma
rotina de tratamento de erros que a rotina normal pode chamar quando ele
esta executado em condicoes de erro.
1 try {
2
...
3
i f ( something unexpected )
4
throw m y e x c e p ti o n ( ) ;

74

5
...
6
c o u t << e v e r y t h i n g s ok \n ;
7
...
8 } catch ( m y e x c e p ti o n ) {
9
c o u t << oops \n ;
10 }

Listing 7.2: C++

Em C ++ e Common Lisp, as excecoes sao todas definidas pelo programador.


No PHP, a funcao set error handler pode ser usado para transformar built-in erros
semanticos em excecoes comuns.
Em Ada, algumas das excepcoes predefinidos podem ser absorvidas por meio de um
pragma.
1 declare empty queue : exception ;

Listing 7.3: Ada


1 EXCEPTION empty queue ;

Listing 7.4: Modula-3

Na maioria das linguagens, um bloco de codigo pode ter uma lista de manipuladores
de excecao. Em C ++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

try { // t r y t o read from f i l e


...
// p o t e n t i a l l y c o m p l i c a t e d s e q u e n c e o f o p e r a t i o n s
// i n v o l v i n g many c a l l s t o stream I /O r o u t i n e s
...
} catch ( e n d o f f i l e ) {
...
} catch ( i o e r r o r e ) {
// h a n d l e r f o r any i o e r r o r o t h e r than e n d o f f i l e
...
} catch ( . . . ) {
// h a n d l e r f o r any e x c e p t i o n not p r e v i o u s l y named
// ( i n t h i s case , t h e t r i p l e d o t e l l i p s i s i s a v a l i d C++ t o k e n ;
// i t d o e s not i n d i c a t e m i s s i n g code )
}

75

7.5

Co-rotinas

Como uma continuacao, um co-rotina e representado por um fechamento (um endereco de codigo e um ambiente de referencia), em que se pode saltar por meio de um
goto nao local. Neste caso, uma operacao especial conhecido como transfer
encia.
A principal diferenca entre as duas abstracoes e que a continuacao e uma constante
de que nao muda, uma vez criada uma co-rotina muda toda vez que e executado.
Quando uma continuacao GOTO, o nosso contador de programa antigo e perdido,
a menos que explicitamente crie uma nova continuacao para manter lo.
Enquanto que a transferencia de uma co-rotina para outra, o nosso contador de
programa antigo e salvo: na co-rotina estamos deixando atualizado para refletir
isso.
Assim, se executarmos GOTO ao mesmo tempo que multiplas continuacoes, cada
salto vai comecar precisamente no mesmo local, mas se executarmos a transferencia
para as mesmas co-rotina varias vezes, cada salto vai ocupar onde o anterior parou.
Porque eles sao concorrentes (isto e, simultaneamente iniciada mas nao concluda),
co-rotinas nao podem compartilhar uma u
nica pilha: as suas chamadas e retornos
de sub-rotinas, tomados como um todo, nao ocorrem no u
ltimo a entrar, primeiro a
sair.
Se cada co-rotina e declarada no nvel mais externo de aninhamento lexical , em
seguida, suas pilhas sao inteiramente disjuntos: os u
nicos objetos compartilhados
sao globais e, portanto, estaticamente alocado.
A maioria dos sistemas operacionais facilitam a atribuicao na pilha, e aumentam a
sua parte do espaco de enderecos virtuais, conforme necessario durante a execucao.
Normalmente nao e facil atribuir um n
umero arbitrario de tais pilhas; espaco para
co-rotinas e um desafio de implementacao.
A solucao mais simples e dar a cada co-rotina uma quantidade fixa de espaco de
pilha alocada estaticamente.
um erro de tempo de execucao se a co-rotina precisar de espaco adicional.
E
Se a co-rotina usa menos espaco do que e dado, o excesso e simplesmente desperdicado.

76

7.6

Eventos

Um evento e algo ao qual um programa em execucao (um processo) precisa de


responder, mas que ocorre fora do programa, em um momento imprevisvel.
Os eventos mais comuns sao entradas para um sistema de interface grafica do usuario
(GUI): teclas, movimentos do mouse, cliques de botao.
Eles tambem podem ser operacoes de rede ou outra atividade I / O assncrono:
a chegada de uma mensagem, a conclusao de uma operacao de disco previamente
solicitado.
Tradicionalmente, os manipuladores de eventos foram implementados em linguagens
de programacao sequenciais como chamadas de sub-rotinas espontaneos, geralmente usando um mecanismo definido e implementado pelo sistema operacional,
fora da linguagem.
Para se preparar para receber eventos atraves deste mecanismo, um programa de
chamar uma rotina de biblioteca, passando como argumento a sub-rotina que quer
ser invocada quando o evento ocorrer.
Ao nvel do hardware, a atividade do dispositivo assncrono durante a execucao ira
disparar um mecanismo de interrupcao que salva registros da rotina, muda para
uma pilha diferente, e salta para um endereco pre-definido no kernel do sistema
operacional.

77

Da mesma forma, se algum outro processo esta sendo executado quando a interrupcao ocorre, o kernel deve salvar o estado do primeiro processo no final da sua
u
ltima fatia de tempo.

78

8 Abstra
c
ao de dados e orienta
c
ao `
a
objetos
8.1

Programac
ao orientada `
a objetos

Com o desenvolvimento de aplicacoes cada vez mais complicadas, abstracao de dados


tornou-se essencial para a engenharia de software.
A abstracao fornecida por modulos e tipos de modulos tem pelo menos tres benefcios
importantes:
1. Ela reduz a carga conceitual, minimizando a quantidade de detalhes que o
programador deve pensar de uma so vez.
2. Ela proporciona contenc
ao falha ao impedir o programador de utilize um
componente de programa de modo inadequado, e atraves da limitacao da
porcao de texto de um programa em que um determinado componente pode
ser utilizado, limitando desse modo a porcao que deve ser considerada quando
se procura a causa de um bug.
3. Ele fornece um significativo grau de independ
encia entre os componentes do
programa, tornando mais facil para atribuir a sua construcao a outros indivduos ou para instala-los em uma biblioteca onde eles podem ser usados por
outros programas.
Programacao orientada a objetos pode ser visto como uma tentativa de aumentar as
oportunidades para a reutilizacao de codigo, tornando mais facil para definir novas
abstracoes como extens
oes ou refinamentos das abstracoes existentes.
Como ponto de partida para os exemplos, considere um conjunto de registros, implementada como uma lista duplamente encadeada.
O exemplo utiliza um estilo de abstracao modulo-como-tipo:
<1-> Cada elemento de uma lista e um objeto de list node.
<2-> A classe contem ambos os membros de dados (prev, em seguida, head node
e val) e sub-rotinas membros (predecessor, sucessor, insert before e remover).
<3-> Membros de sub-rotinas sao chamados m
etodos em muitas linguagens orientadas a objeto;
79

<4-> Membros de dados tambem sao chamados de campos ou atributos.


<5-> A palavra-chave this em C++ se refere ao objeto do qual o metodo atualmente
em execucao e um membro.
<6-> Em Smalltalk e Objective-C, a palavra-chave equivalente e self; em Eiffel e
current.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

c l a s s l i s t e r r { // e x c e p t i o n
public :
char d e s c r i p t i o n ;
l i s t e r r ( char s ) { d e s c r i p t i o n = s ; }
};
class l i s t n o d e {
l i s t n o d e prev ;
l i s t n o d e next ;
l i s t n o d e head node ;
public :
int v a l ; // t h e a c t u a l d a t a i n a node
l i s t n o d e ( ) { // c o n s t r u c t o r
prev = next = head node = t h i s ; // p o i n t t o s e l f
v a l = 0 ; // d e f a u l t v a l u e
}
list node predecessor () {
i f ( prev == t h i s | | prev == head node ) return 0 ;
return prev ;
}
list node successor () {
i f ( next == t h i s | | next == head node ) return 0 ;
return next ;
}
bool s i n g l e t o n ( ) {
return ( prev == t h i s ) ;
}
void i n s e r t b e f o r e ( l i s t n o d e new node ) {
i f ( ! new node>s i n g l e t o n ( ) )
throw new l i s t e r r ( attempt t o i n s e r t node a l r e a d y on l i s t )
;
prev>next = new node ;
new node>prev = prev ;
new node>next = t h i s ;
prev = new node ;
new node>head node = head node ;
}
void remove ( ) {
if ( singleton () )

80

38
39
40
41
42
43
44
45
46
47 } ;

throw new l i s t e r r ( attempt t o remove node not c u r r e n t l y on


l i s t ) ;
prev>next = next ;
next>prev = prev ;
prev = next = head node = t h i s ; // p o i n t t o s e l f
}
l i s t n o d e ( ) { // d e s t r u c t o r
if (! singleton () )
throw new l i s t e r r ( attempt t o d e l e t e node s t i l l on l i s t ) ;
}

Quando criado um objeto com new, um objeto e alocado na pilha; quando criada
atraves de uma declaracao e atribuda estaticamente ou na pilha, dependendo do
tempo de vida.
Em ambos os casos, a criacao faz com que a invocacao de uma rotina de inicializacao
especificada pelo programador, conhecido como um construtor.
Em C++ e seus descendentes, Java e C#, o nome do construtor e a mesma que a
da propria classe.
C++ tambem permite que o programador especifique um metodo destrutor que
sera chamado automaticamente quando um objeto e destrudo, seja por acao programador ou pelo retorno da sub-rotina em que foi declarada.
O nome do processo de destruicao tambem e a mesma que da classe, mas com um
til ( ).
Destruitores sao comumente usados para gerenciamento de armazenamento e verificacao de erros.
O rotulo public na lista dos membros do list node separa membros exigidos pela
implementacao da abstracao de membros disponveis para os usuarios da abstracao.
Membros que aparecem antes do rotulo nao sao.
C++ tambem fornece um rotulo private, de modo que as partes visveis publicamente de uma classe pode ser listado em primeiro lugar.
Em muitas outras lnguas, dados p
ublicos e sub-rotinas membro (campos ou metodos) devem ser individualmente rotulado dessa forma.
Programas orientados a objetos tendem a fazer muitas mais chamadas de sub-rotinas
como programas imperativos comuns, e as sub-rotinas tendem a ser mais curtas.
81

Muitas coisas que seriam realizadas por acesso direto aos campos de registro em uma
linguagem de von Neumann tendem a ser escondido dentro de metodos de objetos
em uma linguagem orientada a objetos.
Muitos programadores de fato considera-lo um estilo ruim para declarar campos
p
ublicos, porque eles dao aos usuarios de uma abstracao acesso direto `a representacao interna.
1 class l i s t n o d e {
2
...
3
int v a l ; // v a l ( l o w e r c a s e v ) i s p r i v a t e
4
public int Val {
5
g e t { // p r e s e n c e o f g e t a c c e s s o r and o p t i o n a l
6
return v a l ; // s e t a c c e s s o r means t h a t Val i s a p r o p e r t y
7
}
8
set {
9
v a l = v a l u e ; // v a l u e i s a keyword : argument t o s e t
10
}
11
}
12
...
13 }

8.2

Encapsulamento e heran
ca

Mecanismos de encapsulamento permitim que o programador agrupe dados e as


sub-rotinas juntos em um so lugar, e para ocultar detalhes irrelevantes dos usuarios
de uma abstracao.
1
2
3
4
5
6
7
8
9
10
11
12
13
14

c l a s s queue : public l i s t { // d e r i v e from l i s t


public :
// no s p e c i a l i z e d c o n s t r u c t o r or d e s t r u c t o r r e q u i r e d
void enqueue ( l i s t n o d e new node ) {
append ( new node ) ;
}
l i s t n o d e dequeue ( ) {
i f ( empty ( ) )
throw new l i s t e r r ( attempt t o dequeue from empty queue ) ;
l i s t n o d e p = head ( ) ;
p>remove ( ) ;
return p ;
}
};

82

A filosofia basica por detras das regras de visibilidade de C++ pode ser resumido
como se segue:
Qualquer classe pode limitar a visibilidade de seus membros. Membros p
ublicos
sao visveis em qualquer lugar da declaracao da classe esta no escopo. Membros
privados sao visveis apenas dentro de metodos da classe. Membros protegidos
sao metodos dentro visveis da classe ou seus descendentes.
A filosofia basica por detras das regras de visibilidade de C++ pode ser resumido
como se segue:
Uma classe derivada pode restringir a visibilidade de membros de uma classe
base, mas nunca pode aumenta-lo. Membros privados de uma classe base nao
sao visveis em uma classe derivada. Membros protegidos e p
ublicos de uma
classe base p
ublica sao protegidos ou p
ublicos, respectivamente, em uma classe
derivada. Membros protegidos e p
ublicos de uma classe base protegida sao
membros protegidos de uma classe derivada. Membros protegido e p
ublicos de
uma classe base privada sao membros privados de uma classe derivada.
A filosofia basica por detras das regras de visibilidade de C++ pode ser resumido
como se segue:
Uma classe derivada que limita a visibilidade dos membros de uma classe base,
declarando que a classe base protegida ou privada pode restaurar a visibilidade
dos membros individuais da classe base, inserindo a declaracao using na parte
da declaracao da classe derivada protegida ou p
ublica.
Muitas linguagens permitem a declaracao de classes aninhardas.
Isso levanta uma questao imediata: se Inner e um membro da Outer, pode metodos
de Inner ver os membros do Outer, e em caso afirmativo, qual instancia que eles
veem?
A resposta mais simples, adotada em C++ e C #, e permitir o acesso apenas aos
membros estaticos da classe externa, uma vez que estes tem apenas uma u
nica
instancia.
Com efeito, aninhamento serve simplesmente como um meio de esconder informacao.
Java tem uma abordagem mais sofisticada. Ele permite que uma classe aninhada
(interna) para acessar membros arbitrarias de sua classe externa.
Cada instancia da classe interna deve, portanto, pertencer a uma instancia da classe
externa.
83

1 c l a s s Outer {
2
int n ;
3
class Inner {
4
public void bar ( ) { n = 1 ; }
5
}
6
Inner i ;
7
Outer ( ) { i = new I n n e r ( ) ; } // c o n s t r u c t o r
8
public void f o o ( ) {
9
n = 0;
10
System . out . p r i n t l n ( n ) ; // p r i n t s 0
11
i . bar ( ) ;
12
System . out . p r i n t l n ( n ) ; // p r i n t s 1
13
}
14 }

8.3

Inicializac
ao e finaliza
c
ao

A maioria das linguagens orientadas a objeto fornecer algum tipo de mecanismo


especial para inicializar um objeto automaticamente no incio de sua vida u
til.
Quando escrito sob a forma de uma sub-rotina, este mecanismo e conhecido como
um construtor.
Embora o nome pode ser pensado para sugerir o contrario, a um construtor nao
alocar espaco; ele inicializa espaco que ja foi atribudo.
Algumas linguagens fornecem um mecanismo de destruicao semelhante ao finalizar
um objeto automaticamente ao final da sua vida u
til. Varias questoes importantes
surgem:
Varias questoes importantes surgem:
Escolhendo um construtor: uma linguagem orientada a objeto pode permitir
que uma classe para ter zero, um, ou muitos construtores distintos. Neste
u
ltimo caso, construtores diferentes podem ter nomes diferentes, ou pode ser
necessario distinguir entre eles pelo n
umero e tipos de argumentos.
Referencias e valores: Se as variaveis sao referencias, em seguida, todos os
objetos devem ser criados explicitamente, e e facil para garantir que um construtor apropriado seja chamado. Se as variaveis sao valores, entao a criacao
de objetos pode acontecer implicitamente como resultado da elaboracao. Neste
u
ltimo caso, a linguagem deve fornecer uma maneira de escolher um construtor
apropriado para cada objeto elaborado.
84

Varias questoes importantes surgem:


Ordem de execucao: Quando um objeto de uma classe derivada e criado em
C++, compilador garante que os construtores para quaisquer classe de base
devera ser executado primeiro, antes de o construtor para a classe derivada.
Alem disso, se uma classe tem membros que sao eles proprios objetos de alguma
classe, em seguida, os construtores para os membros serao chamados antes do
construtor para o objeto em que estao contidos. Estas regras sao uma fonte
de consideravel complexidade sintatica e semantica. Outras lnguas tem regras
mais simples.
A coleta de lixo: A maioria das linguagens orientadas a objeto fornecer algum
tipo de mecanismo de construtor. Destrutores sao comparativamente raros.
Sua principal finalidade e facilitar a recuperacao de armazenamento manual
em linguagens como C++. Se a implementacao da linguagem coleta de lixo
automaticamente, entao a necessidade de destrutores e bastante reduzido.

8.4

M
etodo de vincula
c
ao din
amica

Uma das principais consequencias da extensao heranca / tipo e que uma classe
derivada D tem todos os membros de dados e sub-rotinas de sua classe base C.
Enquanto D nao esconde qualquer um dos membros visveis de C, faz sentido permitir que um objeto da classe D seja usado em qualquer contexto que espera um
objeto da classe C: qualquer coisa que pode fazer em um objeto da classe C tambem
podemos fazer para um objeto da classe D.
A capacidade de usar uma classe derivada em um contexto que espera sua classe
base e chamado polimorfismo de subtipo.
Se imaginarmos um sistema de computacao administrativa para uma universidade,
que pode derivar classes de aluno e professor da classe pessoa:
1
2
3
4
5
6
7
8
9

class person { . . .
c l a s s s t u d e n t : public p e r s o n { . . .
c l a s s p r o f e s s o r : public p e r s o n { . . .
...
student s ;
professor p;
...
p e r s o n x = &s ;
p e r s o n y = &p ;

85

Em Simula, C++ e C#, que usam metodo de vinculacao estatica por padrao, o programador pode especificar que metodos particulares devem usar ligacao dinamica,
rotulando-os como virtual.
Chamadas para metodos virtuais sao enviados para a aplicacao adequada em tempo
de execucao, com base na classe do objeto, em vez de o tipo de referencia.
Em C++ e C#, a palavra-chave virtual e usada como prefixos na declaracao de
sub-rotina.
Com metodo estatico de vinculacao (como em Simula, C++, C#, ou Ada 95), o
compilador pode sempre dizer qual versao de um metodo para chamar, com base no
tipo da variavel a ser usado.
Com o metodo de vinculacao dinamico, no entanto, o objeto referido por uma variavel de referencia ou ponteiro deve conter informacoes suficientes para permitir que
o codigo gerado pelo compilador encontre a versao correta do metodo em tempo de
execucao.
A implementacao mais comum representa cada objeto com um registro cujo primeiro
campo contem o endereco de uma tabela metodo virtual (vtable) para a classe do
objeto.
1 class foo {
2
int a ;
3
double b ;
4
char c ;
5 public :
6
v i r t u a l void k ( . . .
7
v i r t u a l int l ( . . .
8
v i r t u a l void m( ) ;
9
v i r t u a l double n ( . . .
10
...
11 } F ;

86

1 c l a s s bar : public f o o {
2
int w ;
3 public :
4
void m( ) ; // o v e r r i d e
5
v i r t u a l double s ( . . .
6
v i r t u a l char t ( . . .
7
...
8 } B;

8.5

Heranca m
ultipla

` vezes pode ser u


As
til para uma classe derivada para herdar caractersticas de mais
de uma classe base.
Suponha, por exemplo, que queremos que o nosso sistema de computacao administrativa mantenha todos os alunos do mesmo ano em alguma lista. Nesse caso, pode
87

ser desejavel derivar classe estudante, tanto de pessoa e gp list node. Em C ++,
podemos:
1 c l a s s s t u d e n t : public person , public g p l i s t n o d e { . . .

Heranca m
ultipla tambem aparece em CLOS e Python.
Simula, Smalltalk, Objective-C, Modula-3, Ada 95 e Oberon tem heranca simples.
Java, C# e Ruby fornecem uma forma limitada mix-in de heranca m
ultipla, em
que apenas uma classe pai fica autorizada a ter campos.

8.6

Revis
ao da orientada `
a objeto

Diferentes linguagens de programacao apoiam estes conceitos fundamentais em diferentes graus.


Em particular, as linguagens diferem na medida em que exigem que o programador
escreva em um estilo orientado a objetos.
Alguns autores argumentam que uma linguagem verdadeiramente orientada a objeto
deve tornar difcil ou impossvel escrever programas que nao sao orientadas a objeto.
Deste ponto de vista purista, uma linguagem orientada a objeto deve apresentar um
modelo de objeto uniforme da computacao, em que cada tipo de dados e uma classe,
cada variavel e uma referencia a um objeto, e cada sub-rotina e uma metodo.
Alem disso, objetos devem ser pensados em termos antropomorficos: entidades ativas responsaveis por todos os calculos.
Smalltalk e Ruby chegam perto desse ideal.
Mesmo mecanismos de controle de fluxo como a selecao e iteracao sao modelados
como chamadas de metodo em Smalltalk.
Por outro lado, Modula-3 e Ada 95 sao, provavelmente, melhor caracterizada como
linguagens de von Neumann, que permitem ao programador escrever em um estilo
orientado a objeto, se desejar.

88

9 Linguagens funcionais
9.1

Linguagens funcionais

O que e uma linguagens funcional?


Os programas nela codificados consistem inteiramente de funcoes;
Nao tem efeitos colaterais;
A ordem de execucao e irrelevante, ou seja, nao precisa analisar o fluxo de
controle;
Pode-se substituir a qualquer momento variaveis por seus valores;
Os programas nela escritos sao mais trataveis matematicamente;
Programa
Imperativo
(Linguagem de
programacao)

Programa em
linguagem de montagem (Opcode)

Programa
executavel
(Linguagem
de maquina)

89

Programa
Funcional
(Linguagem
funcional)

Programa em
linguagem
intermediaria
(Lambda-calculo)

Programa
executavel
(Linguagem
de maquina)

Programa
Funcional
(Linguagem
funcional)

Maquina abstrata

Programa na
linguagem C

Analisemos a expressao: z = (2 a y + b) (2 a y + c)
Otimizando:
t=2ay

(9.1)

z = (t + b) (t + c)

(9.2)

y =2ay+b

(9.3)

z =2ay+c

(9.4)

Sejam as expressoes:

90

Teremos:
t=2ay

(9.5)

y =t+b

(9.6)

z =t+c

(9.7)

Analisemos a expressao: z = (3ax + b) (3ax + c)

Analisemos a expressao: z = (3ax + b) (3ax + c)

9.2

-express
oes

O conjunto de expressoes, chamadas -expressoes, e definido indutivamente da


seguinte forma:
Todas as variaveis e constantes sao -expressoes (chamada de atomos)
91

Sendo M e N duas -expressoes, entao (M N ) e uma -expressoes, chamada


combinacao ou aplicacao
Sendo M uma -expressao e x uma variavel qualquer, entao (x.M ) e uma
-expressao, chamada abstracao ou funcao.
As -abstracoes sao funcoes nao embutidas e sao construdas atraves do construtor
(). Por exemplo, (x. + x 1) e um -abstracao e der ser definida da seguinte forma:
indica que se trata de uma funcao
x sobre a variavel x
. que
+x 1 adiciona x ao n
umero 1
Exemplo equivalente em C:
1 int i n c ( int x ) {
2
return ( x + 1 ) ;
3 }

A -expressao +3 4 e interpretada como (+3)4, ou seja, uma funcao(+3) que adiciona 3 ao argumento (4).
Esta propriedade se deve ao fato de que o resultado da aplicacao de uma funcao seja
tambem uma outra funcao.
Seja a -expressao (x. + x y)4.
Sabe-se que se trata da aplicacao de uma funcao sobre a variavel x, onde o corpo
da funcao e (+x y), a uma outra -expressao, que no caso e a constante 4
A variavel x pode ser pensada como um local onde o argumento 4 deve ser colocado.
Ja para a variavel y, este mesmo raciocnio nao pode ser aplicado por que nao existe
este local sinalizado por uma variavel y apos a letra .
Uma ocorrencia de uma variavel e ligada se existir uma -abstracao a qual esta
variavel esteja ligada. Em caso contrario ela e livre.

x. + ((y. + y z)7)x
-conversao
92

(9.8)

Analisando as duas -expressoes (x. * x 1) e (y. * y 1), verificamos que elas


representam o mesmo comportamento.
Portanto uma deve ser convertvel na outra

(x. x 1) (y. y 1)

(9.9)

x.x y.y z.z

(9.10)

x.axa b.aba

(9.11)

Podemos deduzir tres relacoes: reflexao, simetria e transitividade


-conversao
O resultado de uma -abstracao a um argumento e uma instancia do corpo
desta -abstracao na qual as ocorrencias livres do parametro formal sao trocadas por copias do argumento.
Por exemplo a aplicacao (x. + x 1)4 e reduzida para +4 1 que e uma instancia
do corpo +x 1, onde trocamos a ocorrencia livre de x pelo argumento 4.
-conversao
Analisemos as duas -abstracoes (x. + 1 x) e (+1).
Verifica-se que elas se comportam da mesma maneira quando aplicadas a um
mesmo argumento.
Elas tambem devem se convertveis uma na outra, ou seja, (x. + 1 x) (+1).
Formalmente, (x.F x) F , desde que x nao ocorra livre em F .
Um redex (reduction expression) e uma -expressao na qual todos os parametros
necessarios que uma operacao possa ser feita estao prontos para ser utilizados.
Se uma expressao nao contiver qualquer redex a sua avaliacao esta completa e, neste
caso, diz-se que a expressao esta na sua forma normal.
Por exemplo, +(3 4)(7 8):

+ (3 4)(7 8)

(9.12)

+12(7 8)

(9.13)

+12 56

(9.14)

68

(9.15)

93

+ (3 4)(7 8)

(9.16)

+(3 4)56

(9.17)

+12 56

(9.18)

68

(9.19)

Algumas observacoes devem ser feitas:


1. Nem toda -expressao tem uma forma normal. Por exemplo, a -expressao
(), onde = (x.x x), tem a seguinte reducao:

(x.xx)(x.xx)
(x.xx)(x.xx)
(x.xx)(x.xx)
...

(9.20)

Correspondendo a um loop infinito.


2. Algumas sequencias de reducao podem atingir a forma normal enquanto outras
nao. Por exemplo, (x.3)().
Na notacao do -calculo nao existem funcoes recursivas.
Isso porque no -calculo as funcoes sao anonimas e nao pode ser chamada recursivamente.
Porem, a implementacao do processador transforma funcoes recursivas em funcao
sem recursividade por meio de pontos fixos da funcao.

9.3

Linguagem Haskell

A linguagem Haskell e baseada em script;


Tambem e uma linguagem interpretada na maioria dos processadores atuais;
case-sensitive;
E
Todas as palavras reservadas sao min
usculas;
A documentacao adequada e download de interpretadores para a linguagem podem
ser encontrado em: <http://www.haskell.org>
Arquivo .hs

94

1 { E s t e a r q u i v o eh um exemplo de um a r q u i v o . hs
2 ####################################}
3
4 v a l o r : : Int
5 v a l o r = 39
6 n o v a l i n h a : : Char
7 novalinha = \n
8 r e s p o s t a : : Bool
9 r e s p o s t a = True
10 maior : : Bool
11 maior = ( v a l o r > 7 1 )
12 quadrado : : Int > Int
13 quadrado x = xx
14 t o d o s I g u a i s : : Int > Int > Int > Bool
15 t o d o s I g u a i s n m p = ( n == m) && (m == p )
16
17 {###################################}

Arquivo .lhs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

E s t e a r q u i v o eh um exemplo de um a r q u i v o . l h s .
#############################
>
>
>
>
>
>
>
>
>
>
>
>

v a l o r : : Int
v a l o r = 39
n o v a l i n h a : : Char
novalinha = \n
r e s p o s t a : : Bool
r e s p o s t a = True
maior : : Bool
maior = ( v a l o r > 7 1 )
quadrado : : Int > Int
quadrado x = xx
t o d o s I g u a i s : : Int > Int > Int > Bool
t o d o s I g u a i s n m p = ( n == m) && (m == p )

###############################

A identacao tem um significado bem preciso:


Se uma linha comeca em pelo menos uma coluna a` frente do inicio da linha
anterior, e considerada a continuacao da linha precedente,
Se uma linha comeca na mesma coluna que a do inicio da linha anterior, elas
sao consideradas definicoes independentes entre si,

95

Se uma linha comeca pelo menos uma coluna anterior do inicio da linha anterior, ela nao pertence a` mesma lista de definicoes.
Principais comandos
Comando
:?
:e
:e exemplo.hs
:l exemplo.hs
:a exemplo.hs
:q

A
c~
ao realizada
Aciona o help
Chama o script atual
Edita o arquivo exemplo.hs
Carrega o script exemplo.hs e limpa outros
arquivos carregados
Carrega o script exemplo.hs sem limpar os
outros arquivos
Termina a se
c~
ao

1 type Par = ( Int , Int )


2 somaAmbos : : Par > Int
3 somaAmbos ( p r i m e i r o , segundo ) = p r i m e i r o + segundo

Letra mai
uscula se for um tipo;
Letra min
uscula se for uma variavel, constante ou funcao.
Lista de palavras reservadas:
case
else
class
hiding
data
if
default import
deriving
in

9.4

infix
module
type
infixl
of
where
infixr
renaming
instance
then
let
to

Func
oes

1
2
3
4
5

+ : : Int > Int > Int


+ v z = v + z

1
2
3
4
5

mmc : : Int > Int > Int


mmc x y = div xy (mdc x y )

t o d o s I g u a i s : : Int > Int > Int > Bool


t o d o s I g u a i s n m p = ( n == m) && (m == p )

mdc : : Int > Int > Int


mdc x y

96

6
| x == y = x
7
| x > y = mdc xy y
8
| otherwise = mdc y x
9
10 div : : Int > Int > Int
11 div x y
12
| x == y = 1
13
| x > y = 1 + div xy y
14
| otherwise = 0

Obedece o sistema leftmost-outermost. Avalia uma expressao somente se ela for


necessaria e somente uma vez.
1
2
3
4
5
6
7

t o d o s I g u a i s ( quadrado 3 ) v a l o r ( quadrado 2 )
= ( ( quadrado 3 ) == v a l o r ) && ( v a l o r == ( quadrado 2 ) )
= ( ( 3 3 ) == v a l o r ) && ( v a l o r == ( quadrado 2 ) )
= ( 9 == v a l o r ) && ( v a l o r == ( quadrado 2 ) )
= ( 9 == 3 9 ) && ( 3 9 == ( quadrado 2 ) )
= False && ( 3 9 == ( quadrado 2 ) )
= False u t i l i z a n d o o mecanismo de a v a l i a c a o de c u r t o c i r c u i t o

1
2
3
4
5
6
7

e Z e r o : : Int > Bool


e Z e r o 0 = True
eZero
= False
f a t : : Int > Int
fat 0 = 1
f a t n = n f a t (n 1)

9.5

Tipos de dados

O tipo inteiro

97

Operador

Tipo

Descri
c~
ao

+, *

Int -> Int -> Int

adi
c~
ao e multiplica
c~
ao

Int -> Int -> Int

exponencia
c~
ao

Int -> Int -> Int

subtra
c~
ao (infixa) e
inversor de sinal (prefixa)

div

Int -> Int -> Int

divis~
ao inteira (prefixa), ou div (infixa)

mod

Int -> Int -> Int

m
odulo (prefixa), ou
mod (infixa)

abs

Int -> Int

valor absoluto de um
inteiro

negate

Int -> Int

troca o sinal de um inteiro

>, >=, ==

Int -> Int -> Bool

operadores elacionais

==, <=, <

Int -> Int -> Bool

operadores relacionais

O tipo ponto flutuante


Operador

Tipo

+, - *
/
^
**
==, /= <, >,<=, >=
abs
acos, asin, atan
ceiling, floor, round
cos, sin, tan
exp
fromInt
log
logBase
negate
read
pi
show
signum
sqrt

Float -> Float -> Float


Float -> Float -> Float
Float -> Int -> Float
Float -> Float -> Float
Float -> Float -> Bool
Float -> Float
Float -> Float
Float -> Float
Float -> Float
Float -> Float
Int -> Float
Float -> Float
Float -> Float -> Float
Float -> Float
String -> Float
Float
* -> String
Float -> Int
Float -> Float

98

Tipo

Descri
c~
ao

Double

Ponto flutuante de precis~


ao dupla

Float

Ponto flutuante de precis~


ao simples

Int

Inteiro sinalizado de precis~


ao fixa (229 . . . 229 1)

Int8

Inteiro sinalizado de 8 bits

Int16

Inteiro sinalizado de 16 bits

Int32

Inteiro sinalizado de 32 bits

Int64

Inteiro sinalizado de 64 bits

Integer

Inteiro sinalizado de precis~


ao arbitr
aria

Rational

N
umeros racionais de precis~
ao arbitr
aria

Word

Inteiro n~
ao sinalizado de precis~
ao fixa

Word8

Inteiro n~
ao sinalizado de 8 bits

Word16

Inteiro n~
ao sinalizado de 16 bits

Word32

Inteiro n~
ao sinalizado de 32 bits

Word64

Inteiro n~
ao sinalizado de 64 bits

O tipo booleano
Fun
c~
ao

Nome

Tipo

&&

and

&& ::

Bool -> Bool -> Bool

||

or

|| ::

Bool -> Bool -> Bool

not

inversor

not ::

Bool -> Bool

O tipo caractere (Char)


Os caracteres sao escritos entre aspas simples.
Existem caracteres especiais:
\t - tabula
c~
ao \
\n - nova linha \

- aspas simples
- aspas duplas

\\ - uma barra invertida \34 - ?

Existem algumas funcoes pre-definidas em Haskell feitas para converter caracteres em n


umeros e vice-versa:
toEnum ::

Int -> Char

fromEnum ::

Char -> Int

O tipo cadeia de caracteres (String)


Ele e um tipo pre-definido como String, mas tambem pode ser considerado
como uma lista de caracteres.
Para satisfazer as duas formas, as strings podem ser escritas entre aspas duplas
ou usando a notacao de lista de caracteres (entre aspas simples).
99

Por exemplo, Constantino tem o mesmo significado que [C, o, n, s, t,


a, n, t, i, n, o], em Haskell.
1 putStr \99 a \116 = c a t
2 putStr Dunga\ t e h o b i c h o = Dunga
eh o b i c h o
3 putStr j e r i ++ coa ++ c o a r a = j e r i c o a c o a r a
1 show (5+7) = 12
2 show (True && False ) = F a l s e
1 read True = True
2 read 14 = 14

9.6

Metodologia de programa
c
ao

Programacao Top-down

Fase de divisao.
menores.

Semana

Vendas

12

14

15

Total

41

M
edia

13.6667

Esta fase consiste na divisao do problema em sub-problemas

Fase de solucao. Esta fase consiste na solucao de cada sub-problema separadamente.


Se o sub-problema ainda for grande, deve ser usada a mesma tecnica de divisao, de
forma recursiva.
Fase de combinacao. Esta fase consiste na combinacao das solucoes dos sub-problemas
em uma solucao u
nica.
Programacao Top-down
1 imprimeTab : : Int > IO ( )
2 imprimeTab n = putStr ( c a b e c a l h o ++ imprimeSemanas n ++ imprimeTotal n
++ imprimeMedia n )
3
4 c a b e c a l h o : : String
5 cabecalho =
\n | | Semana | Vendas | | \
n
\n
6

100

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

imprimeSemanas : : Int > String


imprimeSemanas 0 = imprimeSemana 0
imprimeSemanas n = imprimeSemanas ( n1) ++ imprimeSemana n
imprimeTotal : : Int > String
imprimeTotal n =
show ( t o t a l d e V e n d a s n ) )++ | | \ n

\n | | T o t a l

| ++ r J u s t i f y 6 (

mediaVendas : : Int > Float


mediaVendas n
| n == 0 = fromIntegral ( venda 0 )
| otherwise = fromIntegral ( t o t a l d e V e n d a s n ) / fromIntegral ( n+1)
i m p r i m e e s p a c o s : : Int > String
imprimeespacos 0 =
i m p r i m e e s p a c o s n = ++ i m p r i m e e s p a c o s ( n1)
r J u s t i f y : : Int > String > String
r J u s t i f y n nome
| n > length nome = i m p r i m e e s p a c o s ( n length nome ) ++ nome
| n == length nome = nome
| otherwise = Erro : a s t r i n g nao cabe no i n t e r v a l o dado
imprimeSemana : : Int > String
imprimeSemana n = r J u s t i f y o f f s e t (show n ) ++ | ++
r J u s t i f y ( o f f s e t 2) (show ( venda n ) ) ++ | | \ n
where o f f s e t : : Int
o f f s e t = 10
imprimeMedia : : Int > String
imprimeMedia n = | | Media | ++ r J u s t i f y 9 (show ( mediaVendas n ) )++
| | \ n

Programacao Bottom-up
1
2
3
4

fib
fib
fib
fib

: : Int > Int


0 = 1
1 = 1
n = f i b ( n1) + f i b ( n2)

fib 4
fib 3
fib 2
fib 1

fib 2
fib 1

fib 1

fib 0
101

fib 0

Programacao Bottom-up
1
2
3
4
5
6
7
8
9

f i b b u : : Int > Int


fib bu n
| n > 1 = fu n a u x n 1 1 2
| otherwise = 1
f u n a u x : : Int > Int > Int > Int > Int
fun aux n x y i
| i < n = fu n a u x n y ( x + y ) ( i + 1 )
| otherwise = ( x + y )

Em Haskell, o tipo (t1 , t2 , . . . , tn ) consiste de n-uplas de valores (v1 , v2 , . . . , vn ) onde


v1 :: t1 , v2 :: t2 , . . . , vn :: tn .
Por exemplo,
1
2
3
4
5

type Pessoa = ( String , String , Int )


maria : : Pessoa
maria = ( Maria das Dores , 3225 0000 , 2 2 )
in tP : : ( Int , Int )
in tP = ( 3 5 , 4 5 )

As funcoes sobre tuplas, apesar de poderem ser definidas de varias formas, sao
comumente definidas por pattern matching.
1 somaPar : : ( Int , Int ) > Int
2 somaPar ( x , y ) = x + y

O escopo de uma definicao e a parte de um programa na qual ela e visvel e portanto


pode ser usada.
Em Haskell, o escopo das definicoes e todo o script, ou seja, todo o arquivo no qual
a definicao foi feita.
Por exemplo, vejamos a definicao de ehImpar n, a seguir, que menciona a funcao
ehPar, apesar desta ser definida depois.
Isto so e possvel porque elas compartilham o mesmo escopo.
1
2
3
4
5

ehImpar , ehPar : : Int > Bool


ehImpar 0 = False
ehImpar n = ehPar ( n1)
ehPar 0 = True
ehPar n = ehImpar ( n1)

102

Definicoes locais
Haskell permite definicoes locais atraves da palavra reservada where. Por exemplo,
1 somaQuadrados : : Int > Int > Int
2 somaQuadrados n m = quadN + quadM
3
where
4
quadN = n n
5
quadM = m m

As definicoes locais podem incluir outras definicoes de funcoes e podem usar definicoes
locais a uma expressao, usando a palavra reservada let.
1 l e t x = 3 + 2 ; y = 5 1 in x 2 + 2 xy y

Ao usar a construcao if <expB> then <exp1> else <exp2>, a expressao booleana


<expB> e avaliada e, se o resultado for verdadeiro, a expressao <exp1> e escolhida
para ser avaliada e seu resultado sera o valor da expressao como um todo. S
e, ao contrario, a avaliacao da expressao booleana tiver valor falso, a expressao
<exp2> e escolhida para ser avaliada e seu resultado sera o da expressao.
A exemplo de muitas linguagens de programacao, Haskell tambem suporta a construcao case.
Como exemplo, uma funcao fun que retorna um valor dependendo de sua entrada
pode ser definida por:
1 fun x = case x of
2
0 > 50
3
1 > 100
4
2 > 150
5
> 200

9.7

O tipo Lista

Lista e o tipo de dado mais importante nas linguagens funcionais.


Todas as linguagens funcionais a implementam como um tipo primitivo, juntamente
com uma gama imensa de funcoes para a sua manipulacao.
A notacao utilizada para as listas e colocar seus elementos entre colchetes.
Por exemplo, [1,2,3,4,1,3] e uma lista de inteiros, [True,False] e uma lista de booleanos,
[a, a, b] e uma lista de caracteres e [Marta, Marlene] e uma lista de strings.
103

Ha, no entanto, que se diferenciar as listas homogeneas, que sao as listas onde todos
os valores sao do mesmo tipo, das listas heterogeneas, onde os componentes podem
ter mais de um tipo.
Haskell so admite listas homogeneas.
Por exemplo, [False, 2,Maria] nao e uma lista em Haskell, por ser heterogenea.
Em compensacao, podemos ter a lista [totalVendas, totalVendas], que tem o tipo
[Int -> Int] como tambem a lista [[12,1], [3,4], [4,4,4,4,4], [ ]] que tem o tipo [[Int]],
uma lista de listas de inteiros, alem de outras possibilidades.
Existem duas formas como as listas podem se apresentar:
a lista vazia, simbolizada por [ ], que pode ser de qualquer tipo. Por exemplo,
ela pode ser de inteiros, de booleanos, etc. Dito de outra forma, [ ] e do tipo
[Int] ou [Bool] ou [Int -> Int], significando que a lista vazia esta na intersecao
de todas as listas, sendo o u
nico elemento deste conjunto.
a lista nao vazia, simbolizada por (a : x), onde a representa um elemento da
lista, portanto tem um tipo, e x representa uma lista composta de elementos
do mesmo tipo de a. O elemento a e chamado de cabeca e x e a cauda da lista.
Algumas caractersticas importantes das listas em Haskell, sao:
A ordem em uma lista e importante, ou seja, [1,3] /= [3,1] e [False] /= [False,
False].
A lista [m .. n] e igual a` lista [m, m+1, ..., n]. Por exemplo, [1 .. 5] = [1, 2, 3,
4, 5]. A lista [3.1 .. 7.0] = [3.1, 4.1, 5.1, 6.1].
A lista [m,p .. n] e igual a` lista de m ate n em passos de p-m. Por exemplo,
[7,6 ..3] = [7, 6, 5, 4, 3] e [0.0, 0.3 ..1.0] = [0.0, 0.3, 0.6, 0.9].
A lista [m..n], para m>n, e vazia. Por exemplo, [7 .. 3] = [ ].
A lista vazia nao tem cabeca e nem cauda. Se tivesse qualquer destes dois
componentes, nao seria vazia.
A lista nao vazia tem cabeca e cauda, onde a cauda e tambem uma lista, que
pode ser vazia, ou nao.
1 somaLista : : [ Int ] > Int
2 somaLista [ ] = 0
3 somaLista ( a : x ) = a + somaLista x

104

O construtor de listas, chamado de cons e sinalizado por : (dois pontos), tem importancia fundamental na construcao de listas.
Ele e um operador que toma como argumentos um elemento, de um tipo, e uma
lista de elementos deste mesmo tipo e insere este elemento como a cabeca da nova
lista.
Por exemplo,
1 10 : [ ] = [ 1 0 ]
2 2 : 1 : 3 : [ ] = 2 : 1 : [ 3 ] = 2 : [1 ,3] = [2 ,1 ,3

Fun
c~
ao

Tipo

Exemplo

:
++
!!
concat
length
head
last
tail
init
replicate
take
drop
splitAt
reverse
zip
unzip
and
or
sum

a -> [a] -> [a]


[a] -> [a] -> [a]
[a] -> Int -> a
[[a]] -> [a]
[a] -> Int
[a] -> a
[a] -> a
[a] -> [a]
[a] -> [a]
Int -> a -> [a]
Int -> [a] -> [a]
Int -> [a] -> [a]
Int -> [a] -> ([a], [a])
[a] -> [a]
[a] -> [b] -> [(a, b)]
[(a, b)] -> ([a], [b])
[Bool] -> Bool
[Bool] -> Bool
[Int] -> Int
[Float] -> Float
[Int] -> Int
[Float] -> Float

3:[2,5]=[3,2,5]
[3,2]++[4,5]=[3,2,4,5]
[3,2,1]!!0=3
[[2],[3,5]]=[2,3,5]
length [3,2,1]=3
head [3,2,5]=3
last [3,2,1]=1
tail [3,2,1]=[2,1]
init [3,2,1]=[3,2]
replicate 3 a=[a,a,a]
take 2 [3,2,1]=[3,2]
drop 2 [3,2,1]=[1]
splitAt 2 [3,2,1]=([3,2],[1])
reverse [3,2,1]=[1,2,3]
zip[3,2,1][5,6]=[(3,5),(2,6)]
unzip [(3,5),(2,6)]=([3,2],[5,6])
and [True,False]=False
or [True,False]=True
sum [2,5,7]=14
sum [3.0,4.0,1.0]=8.0
product [1,2,3]=6
product [1.0,2.0,3.0]=6.0

product

Vamos construir uma funcao que verifica se um determinado elemento pertence, ou


nao, a uma lista.
Para isto vamos construir a funcao pertence:
1 p e r t e n c e : : Int > [ Int ] > Bool
2 p e r t e n c e b [ ] = False
3 p e r t e n c e b ( a : x ) = ( b == a ) | | p e r t e n c e b x

Esta mesma funcao tambem pode ser codificada de outra forma, usando guardas:
1 p e r t e n c e b [ ] = False
2 pertence b (a : x)
3
| b == a = True
4
| otherwise = p e r t e n c e x b

105

Vamos definir uma funcao de ordenacao, ordena, que utiliza uma funcao auxiliar
insere, cuja tarefa e inserir cada elemento da lista no lugar correto.
1
2
3
4
5
6
7
8
9

ordena : : [ Int ] > [ Int ]


ordena [ ] = [ ]
ordena ( a : x ) = i n s e r e a ( ordena x )
i n s e r e : : Int > [ Int ] > [ Int ]
insere a [ ] = [ a ]
insere a (b : y)
| a <= b = a : ( b : y )
| otherwise = b : i n s e r e a y

As compreensoes, tambem conhecidas como expressoes ZF (Zermelo-Fraenkel).


O domnio desta tecnica permite ao programador resolver muitos problemas de
maneira simples.
A sintaxe das expressoes ZF e muito proxima da descricao matematica de conjuntos
por intensionalidade, exprimindo determinadas propriedades.
Vamos supor que ex = [2,4,7]. Usando ex, podemos construir ex1, a lista cujos
elementos sejam o dobro dos elementos de ex, da seguinte forma:
1 ex1 = [ 2 a | a<ex ]

Desta forma ex1 = [4,8,14]. Se quizermos encontrar a lista ex2 composta dos elementos de ex que sejam pares, podemos declarar
1 ex2 = [ a | a<ex , a mod 2 == 0 ]

1 q u i c k s o r t : : Ord t => [ t ] > [ t ]


2 quicksort [ ] = [ ]
3 q u i c k s o r t ( z : x ) = q u i c k s o r t menores ++ [ z ] ++ q u i c k s o r t m a i o r e s
4
where
5
menores = [ y | y < x , y <= z ]
6
m a i o r e s = [ y | y < x , y > z ]

a ideia de listas de funcoes ou de funcoes que retornam outras funcoes com resul E
tados.
Esta e uma caracterstica importante das linguagens funcionais.
A ideia central e a de que as funcoes sao consideradas com os mesmos direitos que
qualquer outro tipo de dado, dizendo-se, corriqueiramente, que elas sao cidadas de
primeira categoria.
106

Vamos imaginar uma funcao twice que, quando aplicada a uma outra funcao, por exemplo, f, produza, como resultado, uma outra funcao que aplicada a seu argumento
tenha o mesmo efeito da aplicacao da funcao f, duas vezes. Assim,
1 twice f x = f ( f x)

Como outro exemplo, vamos considerar a seguinte definicao em Haskell:


1 l e t s u c = soma 1
2
soma x = somax
3
where somax y = x + y
4 in s u c 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

tuplaNum : : [ Char ] > ( [ Char ] , Int )


tuplaNum s = ( s , length s )
l i s t a T u p l a : : [ String ] > [ ( String , Int ) ]
listaTupla [ ] = [ ]
l i s t a T u p l a ( s : xs ) = ( tuplaNum s ) : l i s t a T u p l a xs
dobra : : Int > Int
dobra n = 2n
d o b r a L i s t a : : [ Int ] > [ Int ]
dobraLista [ ] = [ ]
d o b r a L i s t a ( n : x ) = ( dobra n ) : d o b r a L i s t a x
l i s t a T u p l a : : [ String ] > [ ( String , Int ) ]
l i s t a T u p l a xs = map tuplaNum xs
d o b r a L i s t a : : [ Int ] > [ Int ]
d o b r a L i s t a x = map dobra x

Uma alternativa, possvel em Haskell, consiste em declarar uma funcao apenas no


ponto de chamada, sem ligar esta funcao a algum identificador.
Estas funcoes sao chamadas de funcoes anonimas e se baseiam na notacao do
-calculo.
Esta forma de definicao de funcoes e mais compacta e mais eficiente. As funcoes
dobraLista e listaTupla podem ser definidas da seguinte forma:
1 d o b r a L i s t a l = map ( \ n > 2n ) l
2 l i s t a T u p l a l s = map ( \ s > ( s , length s ) ) l s

107

Tipos variaveis
Quando uma funcao tem um tipo envolvendo um ou mais tipos variaveis, diz-se
que ela tem um tipo polimorfico.
Para explicitar esta caracterstica denota-se que [ ] :: [t], sendo t e uma variavel
que pode assumir qualquer tipo.
Assim, cons tem o tipo polimorfico (:) :: t -> [t] -> [t]. As seguintes funcoes,
algumas ja definidas anteriormente, tem os tipos:
1
2
3
4
5

length : : [ t ] > Int


(++) : : [ t ] > [ t ] > [ t ]
r e v : : [ t ] > [ t ]
id : : t > t
zip : : [ t ] > [ u ] > [ ( t , u ) ]

O tipo mais geral


Alguma dificuldade pode surgir nas definicoes dos tipos das funcoes quando
elas envolvem tipos variaveis, uma vez que podem existir muitas instancias de
um tipo variavel.
Para resolver este dilema, e necessario que o tipo da funcao seja o tipo mais
geral possvel, que e definido da seguinte forma:
Definicao. Um tipo w de uma funcao f e o tipo mais geral de f se todos os
tipos de f forem instancias de w.
A partir desta definicao pode-se observar que o tipo [t] -> [t] -> [(t, t)] e uma
instancia do tipo da funcao zip, mas nao e o tipo mais geral, porque [Int] ->
[Bool] -> [(Int, Bool)] e um tipo para zip, mas nao e uma instancia de [t]->[t]
->[(t,t)]
Uma forma simples de estruturar um programa e constru-lo em etapas, uma apos
outra, onde cada uma delas pode ser definida separadamente.
Em programacao funcional, isto e feito atraves da composicao de funcoes, uma
propriedade matematica so implementada nestas linguagens.
A expressao f(g(x)), normalmente, e escrita pelos matematicos como (f.g)(x), onde
o . (ponto) e o operador de composicao de funcoes.
Esta notacao e importante porque separa a parte das funcoes da parte dos argumentos. O operador de composicao e um tipo de funcao, cujos argumentos sao duas
funcoes e o resultado e tambem uma funcao.
108

Como mais um exemplo, vamos considerar a definicao


1 l e t quad = compoe ( quadrado , s u c e s s o r )
2
quadrado x = x x
3
sucessor x = x + 1
4
compoe ( f , g ) = h where h x = f ( g ( x ) )
5 in quad 3

A resposta a esta aplicacao e 16. O interesse maior aqui esta na definicao da funcao
compoe.
Ela toma um par de parametros, f e g (ambos funcoes) e retorna uma outra funcao,
h, cujo efeito de sua aplicacao e a composicao das funcoes f e g.
Esta forma de codificacao torna a composicao explcita sem a necessidade de aplicar
cada lado da igualdade a um argumento.
Como e sabido, nem todo par de funcoes pode ser composto.
O tipo da sada da funcao g tem de ser o mesmo tipo da entrada da funcao f. O
tipo de . e:
( . ) :: (u -> v) -> (t -> u) -> (t -> v).
A composicao e associativa, ou seja, f . (g . h) = (f . g) . h, que deve ser interpretado
como faca h, depois faca g e finalmente faca f.
As secoes sao operacoes parciais, normalmente relacionadas com as operacoes aritmeticas. Nas aplicacoes, elas sao colocadas entre parenteses. Por exemplo,
(+2) e a funcao que adiciona algum argumento a 2,
(2+) a funcao que adiciona 2 a algum argumento,
(>2) a funcao que retorna True se um inteiro for maior que 2,
(3:) a funcao que coloca o inteiro 3 na cabeca de uma lista,
(++\n) a funcao que coloca o caractere \n ao final de uma string.
Uma secao de um operador op coloca o argumento no lado que completa a aplicacao.
Por exemplo, (op a) b = b op a e (a op) b = a op b.

109

9.8

Classes de tipos

Os tipos sobre os quais uma funcao sobrecarregada p o de atuar formam uma colecao
de tipos chamada classe de tipos (type class) em Haskell.
Quando um tipo pertence a uma classe, diz-se que ele e uma instancia da classe.
As classes em Haskell permitem uma hierarquia entre elas, juntamente com um
mecanismo de heranca, de forma similar ao mecanismo de heranca encontrado nas
linguagens orientadas a objeto.
A funcao elem, definida a seguir, quando aplicada a um elemento e a uma lista de
valores dotipo deste elemento, verifica se ele pertence ou nao a` lista, retornando um
valor booleano.
1 elem : : t > [ t ] > Bool
2 elem x [ ] = False
3 elem x ( a : y ) = x == a | | elem x y

Analisando a definicao desta funcao aplicada `a lista nao vazia, verificamos que e
feito um teste para verificar se o elemento x e igual a cabeca da lista (x==a).
Isto implica que a funcao elem so pode ser aplicada a tipos cujos valores possam ser
comparados pela funcao ==.
Os tipos que tem esta propriedade formam uma classe em Haskell.
Em Haskell, existem dois operadores de teste de igualdade: == e /=.
Para valores booleanos, eles sao definidos da seguinte forma:
1 (/=) , (==) : : Bool > Bool > Bool
2 x == y = ( x and y ) or ( not x and not y )
3 x /= y = not ( x == y )

Desejamos que == e /= sejam operadores sobrecarregados.


Estas operacoes serao implementadas de forma diferente para cada par de tipos e a
forma adequada de introduzi-las e declarar uma classe que contenha todos os tipos
para os quais == e /= vao ser definidas.
Esta classe e pre-definida em Haskell e e denominada Eq.
A forma de declarar Eq como a classe dos tipos que tem os operadores == e /= e
a seguinte:
110

1 c l a s s Eq t where
2 (==) , (/=) : : t > t > Bool

Foi visto que o teste de igualdade (==) e sobrecarregado, o que permite que ele
seja utilizado em uma variedade de tipos para os quais esteja definido, ou seja, para
instancias da classe Eq.
Sera mostrado agora como as classes e instancias sao declaradas, por exemplo, a
classe Visible que transforma cada valor em um String e da a ele um tamanho.
Esta classe e necessaria porque o sistema de entrada/sada de Haskell so permite a
impressao de Strings, ou seja, para que qualquer valor em Haskell seja impresso, e
necessario que ele seja primeiro transformado em um String, caso ainda nao o seja
1 c l a s s V i s i b l e t where
2 t o S t r i n g : : t > String
3 s i z e : : t > Int

A definicao inclui o nome da classe (Visible) e uma assinatura, que sao as funcoes
que compoem a classe juntamente com seus tipos.
Um tipo t para pertencer a` classe Visible tem de implementar as duas funcoes da
assinatura, ou seja, coisas visveis sao coisas que podem ser transformadas em um
String e que tenham um tamanho.
Por exemplo, para se declarar que o tipo Char seja uma instancia da classe Visible,
deve-se fazer a declaracao
1 instance V i s i b l e Char where
2 t o S t r i n g ch = [ ch ]
3 size
= 1

Uma classe em Haskell pode herdar as propriedades de outras classes, como nas
linguagens orientadas a objetos.
Como exemplo, vamos observar a classe Ord que possui as operacoes >, >=, <,
<=, max, min e compare, alem de herdar a operacao == da classe Eq.
Sua definicao e feita da seguinte forma:

111

1
2
3
4

c l a s s Eq t => Ord t where


(<) , (<=) , (>) , (>=) : : t > t > Bool
max, min : : t > t > t
compare : : t > t > Ordering

Neste caso, a classe Ord herda as operacoes de Eq (no caso, == e /=).


Ha a necessidade de se definir pelo menos a funcao < (pode ser outra) para ser
utilizada na definicao das outras funcoes da assinatura.
Estas definicoes formam o conjunto de declaracoes a seguir:
1 x <= y = ( x < y | | x == y )
2 x > y = y < x
3 x >= y = ( y < x | | x == y )

9.9

Tipos alg
ebricos

Haskell oferece uma forma especial para a construcao de novos tipos de dados
visando modelar situacoes, como:
as enumeracoes implementadas em algumas linguagens de programacao;
tipos cujos elementos sejam n
umeros ou Strings;
as estruturas de arvores;
as estruturas de grafos.
Para construir um novo tipo de dados, usamos a declaracao data que descreve como
os elementos deste novo tipo sao construdos.
Cada elemento e nomeado por uma expressao formulada em funcao dos construtores
do tipo.
Alem disso, nomes diferentes denotam elementos tambem distintos.
Atraves do uso de pattern matching sobre os construtores, projetam-se operacoes
que geram e processam elementos do tipo de dados escolhidos.
Os tipos assim descritos, nao as operacoes, sao chamados tipos concretos e sao
modelados em Haskell atraves dos tipos algebricos.
A sintaxe da declaracao de um tip o algebrico de dados e

112

1 data <C o n s t t i p o > = <Const Val1> | <Const Val2> |


>

...

| <Const Valn

onde Const tipo e o construtor do tipos e Const Val1, Const Val2, ... Const Valn
sao os elementos ou valores do tipo.
Cada valor do tipo e composto de um construtor de valor, tambem conhecido por
construtor de dado, seguido, ou nao, de componentes do valor.
Vamos mostrar um exemplo caracterizando cada um destes elementos.
1 data P u b l i c a c a o = L i v r o Int String [ String ]
2
| R e v i s t a String Int Int
3
| A r t i g o String String Int
4
deriving (Show)

Por exemplo, em C, o tipo enumeracao dia util pode ser definido da seguinte forma:
1 enum d i a u t i l { segunda , t e r c a , quarta , quinta , s e x t a } ;

Este tipo pode ser definido, em Haskell, como um tipo algebrico, com a vantagem
da ausencia dos bugs citados. Senao vejamos:
1 data D i a u t i l = Segunda
2
| Terca
3
| Quarta
4
| Quinta
5
| Sexta
6
deriving (Eq, Show)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

enum forma { c i r c u l o , r e t a n g u l o } ;
struct ponto { f l o a t x , y } ;
struct c i r c u l o {
struct ponto c e n t r o ;
float raio ;
};
struct r e t a n g u l o {
struct ponto c a n t o ;
float base ;
float altura ;
};
struct f i g u r a {
enum forma t i p o ;
union {
struct c i r c u l o m e u c i r c u l o ;

113

16
struct r e t a n g u l o meuretangulo ;
17
} fig gemetrica ;
18 } ;
1 data Ponto = ( Float , Float )
2 data F i g u r a = C i r c u l o Ponto Float
3
| Retangulo Ponto Float Float

Tipos recursivos
Uma expressao aritmetica simples que envolve apenas adicoes e subtracoes de
valores inteiros pode ser modelada atraves de sua BNF.
Usando um tipo algebrico podemos modelar uma expressao da seguinte forma:
1 data Expr = L i t Int | Add Expr Expr | Sub Expr Expr

Podemos criar uma funcao de avaliacao que tome como argumento uma expressao e de como resultado o valor da expressao.
Para isso podemos construir a funcao eval da seguinte forma:
1
2
3
4

eval
eval
eval
eval

: : Expr
( Lit n)
(Add e1
( Sub e1

> Int
= n
e2 ) = ( e v a l e1 ) + ( e v a l e2 )
e2 ) = ( e v a l e1 ) ( e v a l e2 )

114

10 Linguagens l
ogicas
10.1

Linguagens l
ogicas

Sistemas de programacao em logica permitem que o programador indique uma


colecao de axiomas a partir do qual teoremas podem ser comprovados.
O usuario de um programa logico afirma um teorema, ou objetivo, e a implementacao da linguagem tenta encontrar um conjunto de axiomas e passos de inferencia
(incluindo opcoes de valores para as variaveis) que, juntos, implicam um objetivo.
Das varias linguagens logicas existentes, Prolog e, de longe, a mais utilizada.
Em quase todas as linguagens logicas, axiomas sao escritas em uma forma padrao
conhecido como uma cl
ausula de Horn.
Uma clausula de Horn consiste de uma cabeca, ou consequente termo H, e um
corpo que consiste em Bi termos:

H B1 , B2 , . . . , Bn

(10.1)

A semantica desta declaracao e que quando os Bi sao todos verdadeiros, podemos


deduzir que H e verdadeiro tambem.
Quando lemos em voz alta, dizemos H, se B1 , B2 , . . . e Bn .
As clausulas Horn podem ser usadas para capturar a maioria, mas nao todas, as
instrucoes logicas.
A fim de obter novas informacoes, um sistema de programacao logica combina
declaracoes existentes, cancelando termos semelhantes, por meio de um processo
conhecido como resoluc
ao.
Se sabemos que A e B implica C, por exemplo, e que implica C implica D, podemos
deduzir que A e B implica D:

C A, B

(10.2)

DC

(10.3)

D A, B

(10.4)

115

Em geral, termos como A, B, C e D podem resultar nao apenas de constantes


(Rochester e chuvoso), mas tambem de predicados aplicados a
atomos ou vari
aveis: rainy(Rochester), rainy(Seattle), rainy(X).
Durante a resolucao, variaveis livres podem adquirir valores por meio da unifica
c
ao
com expressoes em termos de combinacao

10.2

f lowery(X) rainy(X)

(10.5)

rainy(Rochester)

(10.6)

f lowery(Rochester)

(10.7)

Linguagem Prolog

A Programacao em Logica faz parte do paradigma de programacao denominado


declarativo ou descritivo, neste, deve-se implementar uma descricao do problema e
nao um conjunto de instrucoes.
A linguagem Prolog e uma representante do paradigma declarativo, esta e a representante mais famosa da Programacao em Logica, a qual se baseia no calculo de
predicados.
Prolog foi criada em 1972 por Colmerauer e Roussel, um programa Prolog nao possui
codigo para manipular a memoria ou realizar desvios condicionais.
Isso nao significa que Prolog seja superior a`s outras linguagens, pode-se afirmar
apenas que a linguagem e mais adequada para solucionar uma determinada categoria
de problemas.
Os problemas diz respeito onde e necessario representar algum tipo de conhecimento,
por exemplo, em aplicacoes que realizem computacao simbolica, na compreensao de
linguagem natural ou em sistemas especialistas.
Um programa Prolog constitui-se de uma colecao de fatos (base de dados) e regras
(relacoes logicas), esses itens descrevem o domnio de um determinado problema.
Esta descricao do problema e avaliada por um interpretador, o qual utilizando um
motor de inferencia realiza deducoes em busca de conclusoes validas para consultas
realizadas pelos usuarios.
Assim, pode-se afirmar que a computacao destes programas e equivalente a prova
de um teorema em logica.
116

Os fatos de Prolog permitem a definicao de predicados por meio da declaracao de


quais itens pertencentes ao universo (ou domnio) satisfazem os predicados.
Por exemplo, pode-se definir o predicado homem(x) e utilizar este para definir quais
elementos do universo possuem tal predicado, no caso x e homem. Vale salientar que
e responsabilidade do programador manter a definicao de um predicado consistente.
Por exemplo, poderia ser criado o predicado come(x,y) para representar que x come y
ou y come x, o programador e que devera especificar os fatos de maneira consistente.
As regras Prolog sao descricoes de predicados por meio de condicionais. Por exemplo,
pode-se definir o predicado pai(x), significando que x e pai, atraves da regra: pai(X)
:- prole(X, ), homem(X). A regra significa que x e pai se x possui ao menos um filho.
O usuario interage com o programa atraves de consultas (queries). Por exemplo,
sejam dados os fatos:
1
2
3
4

homem( pedro ) .
homem( j o a o ) .
mulher ( maria ) .
mulher ( t e r e s a ) .

O usuario pode realizar a consulta homem(X). e receber as seguintes respostas


X=pedro;
X=joao; No, significando que pedro e joao sao homens, o No indica que nao existem
mais respostas que satisfacam a consulta.
As computacoes em Prolog utilizam os conceitos de clausulas de horn, resolucao e
encadeamento para tras (backtracking), com estes e possvel realizar a computacao
de maneira equivalente a uma deducao em Logica de 1a ordem.
Tanto fatos quanto regras sao representados atraves de clausulas de horn, ou seja, sao
formulas que contem predicados ou negacao de predicados conectados por disjuncoes,
onde ao menos um predicado nao e uma negacao.
Quantificadores nao sao representados explicitamente, porem, a linguagem trata
uma regra como se ela estivesse universalmente quantificada.
Utilizando a regra de inferencia da particularizacao universal repetidas vezes, e
possvel retirar os quantificadores e fazer com que uma variavel assuma qualquer
valor do domnio de representacao.
Para realizar uma deducao, Prolog utiliza unificacao e a regra de inferencia da
resolucao.
117

Assim, duas clausulas dao origem a um resolvente quando possuem predicados correspondentes, sendo um positivo (nao negado) e outro negativo (negado).
Quando o usuario realiza uma consulta o motor de inferencia tenta resolver metas,
sendo que uma meta pode conter submetas, quando nao existem mais metas para
serem satisfeitas em uma linha de resolucao, o sistema utiliza encadeamento para
tras (backtracking) em busca de outras respostas possveis.
Win-Prolog
respeita a sintaxe de Edinburgo, e a implementacao mais proxima do dialeto
um sistema comercial, mas possui uma versao para testes gratuita.
puro. E
Open Prolog
disponvel unicamente para Macintosh (Mac OS 7.5 ou superior). Respeita a
sintaxe padrao ISO, baseada na sintaxe de Edinburgo.
Ciao Prolog
distribudo gratuitamente, possui licenca Library General Public License (LGPL).
Respeita a sintaxe padrao ISO, pode ser utilizada em diversos sistemas operacionais, tais como, Windows (98, NT, 2k, XP), Linux, SunOS, Solaris, MacOS
X, entre outros.
YAP Prolog
criado pela Universidade do Porto em parceria com a Universidade Federal do
Rio de Janeiro. Uma das principais caractersticas desta implementacao e sua
velocidade. Respeita a sintaxe padrao ISO, tambem e compatvel com outras
implementacoes de Prolog, tais como, Quintus e SICStus.
SWI Prolog
software gratuito, sob a licenca Lesser GNU Public License. Pode ser utilizado
nas plataformas Windows, Linux e MacOS. Possui diversas ferramentas de
edicao graifca, tais como, J-Prolog Editor e SWI-Prolog-Editor (recomendado).
Permite a utilizacao da linguagem Prolog por outras linguagens, tais como,
C/C++ e Java.
SICStus Prolog
e um software comercial, mas possui versao gratuita para avaliacao. Respeita
a sintaxe padrao ISO e pode ser usada nas plataformas Windows (2k e XP),
Linux, Solaris 7, MacOS X e algumas distribuicoes Unix.
118

Amzi! Prolog
pode ser utilizado de maneira conjunta a IDE Eclipse, permite tambem comunicacao com outras linguagens. Possui distribuicoes gratuitas e comerciais, que
podem ser utilizadas em sistemas operacionais diversos, tais como, Windows,
Linux, Solaris e HP/UX.
Visual Prolog
bastante diferente da versao padrao de Prolog, e fortemente tipado. Tambem
conhecido como PDC Prolog ou Turbo Prolog, possui distribuicoes gratuitas e
comerciais para Windows e Linux.
Os dados representados em Prolog podem ser um dos seguintes tipos:
variaveis - devem ser iniciados com letras mai
usculas ou underscore ( ), seguidos
de qualquer caractere alfanumerico. Caso uma variavel seja definida apenas
com underscore, ela sera considerada uma variavel anonima, ou seja, nao se
deseja saber seu valor. Ex.: X, Y1, Nome, ...;
nao variaveis;
atomicas;
atomos - sao constantes expressas atraves de palavras. Devem ser iniciados
com letra min
uscula seguida de qualquer caractere alfanumerico. Caso seja
necessario definir um atomo com letra mai
uscula ou n
umero, deve se usar
aspas simples. Ex.: joao, Joao, 16, Marys, ...;
inteiros - qualquer n
umero que nao contenha um ponto (.) sera considerado
um inteiro. Caracteres ASCII entre aspas duplas sao considerados inteiros.
Ex.: 1, 6, -3, a (interpretado como 97), ...;
n
umeros em ponto flutuante - qualquer n
umero com um ponto e pelo menos
uma casa decimal. Ex.: 5.3 (correto), 7.8 (correto), 7. (incorreto);
nao atomicas;
listas - e uma sequencia de elementos ordenados. Uma lista e declarada
entre colchetes e os elementos devem ser separados por vrgula. Pode-se
separar a cabeca (1o. elemento) do corpo (demais elementos) de uma lista
utilizando |. Ex.: [ a, b, c], [a | b, c ], ... .
O comando write exibe o valor do parametro no dispositivo de sada corrente. O
dispositivo padrao e o monitor, assim, o comando write(Teste de impressao.). ira
exibir a mensagem Teste de impressao. na tela do monitor.
119

O mesmo comando pode ser utilizado para imprimir o valor de qualquer variavel.
No entanto, nao existe um comando padrao Prolog para escrita de expressoes formatadas.
Devido a isso, o SWI-Prolog utiliza comandos de extensao, um deste e o comando
writef do C-Prolog de Edinburgo. Este comando possui a seguinte sintaxe writef(Formato,
Argumentos).
Onde as opcoes para determinar a formatacao sao:
%w - imprime o termo;
%d - imprime o termo ignorando seu tipo, por exemplo, \n e impresso como
uma string.
%s - imprime o termo como uma string;
%Nc - imprime o termo de modo centralizado numa quantidade N de colunas;
%Nl - imprime o termo alinhado a` esquerda numa quantidade N de colunas;
%Nr - imprime o termo alinhado a` direita numa quantidade N de colunas;
Para gerar alguns caracteres deve se usar sequencias de escape, estas sao:
\n - cria uma nova linha;
\l - criar um separador de linha, o resultado e igual ao produzido por \n;
\r - retorna ao incio da linha;
\t - tabulacao;
\% - imprime o smbolo %;
\nnn - onde n e um n
umero decimal, produz o caractere ASCII com o codigo
informado.
O comando read le um valor no dispositivo de entrada corrente e unifica (atribui) o
valor uma variavel. O dispositivo de entrada padrao e o teclado, assim, o comando
read(X). ira ler um valor do teclado e unificar este valor com a variavel X.
Em Prolog existem dois tipos de comentarios, estes sao identificados pelos smbolos
% e /* */.
O smbolo % expressa que tudo aquilo que estiver entre ele e o final da linha deve
ser tratado como comentario.
Os smbolos /* */ indicam que tudo que estiver entre /* e */ sera tratado como
comentario, pode-se observar exemplos de comentarios no trecho de codigo abaixo:
120

1 % D e s c r i c a o dos p r e c i d a d o s homem e mulher s o b r e t o d o s os e l e m e n t o s do


u n i v e r s o de r e p r e s e n t a c a o .
2 homem( pedro ) . % r e p r e s e n t a o f a t o de que pedro eh homem .
3 homem( j o a o ) . % r e p r e s e n t a o f a t o de que j o a o eh homem .
4 mulher ( maria ) . % r e p r e s e n t a o f a t o de que maria eh mulher
5 mulher ( t e r e s a ) . % r e p r e s e n t a o f a t o de que t e r e s a eh mulher

Todo comando enviado para o interpretador deve obrigatoriamente ser finalizado


pelo caractere ponto (.).
Por exemplo, o comando
1 ? write ( T e s t e )

nao resultara em uma impressao, enquanto o comando


1 ? write ( T e s t e ) .

resultara na impressao da string Teste.


O smbolo ?- sera usado nos exemplos e apresentados para representar o prompt de
comandos, ele nao deve ser digitado.

10.3

Fatos, regras e consultas


Pam

Tom

Bob

Liz

Ann

Pat

Jim

Para se representar estas relacoes em Prolog, inicialmente pode-se criar a relacao


genitor(x, y), significando que x e genitor de y.
Entao, podemos inserir na janela de edicao do programa os seguintes fatos:
121

1
2
3
4
5
6

genitor
genitor
genitor
genitor
genitor
genitor

(pam , bob ) .
( tom , bob ) .
( tom , l i z ) .
( bob , ann ) .
( bob , pat ) .
( pat , jim ) .

Apos a definicao desta relacao podem ser realizadas consultas no sistema, para isso
basta clicar no botao de consulta, e digitar perguntas no prompt de comandos, tal
como exibido a seguir:
1
2
3
4

3 ? g e n i t o r ( pat , jim ) .
Yes
4 ? g e n i t o r ( jim , pat ) .
No

A consulta rotulada com o n


umero 3, retorna a resposta yes (sim), isso ocorre pois
o motor de inferencia encontrar o fato correspondente e responder positivamente.
Ja para a pergunta rotulada com o n
umero 2, a resposta retornada e No (nao), pois
o programa nao possui nenhum fato que indique que pat e genitora de jim.
As consultas apresentadas exemplificam o tipo mais simples de consulta, quando
um fato da base ja satisfaz a pergunta.
Consultas mais interessantes sao possveis, por exemplo, pode-se indagar quem sao
os genitores de um determinado sujeito, ou quem sao os filhos de deste na base de
fatos do exemplo.
Para isso, pode-se digitar no prompt de comandos as seguintes perguntas:
1
2
3
4
5
6
7
8
9
10
11
12
13

5 ? g e n i t o r (X, bob ) .
X = pam ;
X = tom ;
No
6 ? g e n i t o r ( bob , X) .
X = ann ;
X = pat ;
No
7 ? g e n i t o r (X,Y) .
X = pam
Y = bob ;
X = tom
Y = bob ;

122

14
15
16
17
18
19
20
21
22

X =
Y =
X =
Y =
X =
Y =
X =
Y =
No

tom
liz ;
bob
ann ;
bob
pat ;
pat
jim ;

a consulta rotulado com o n


umero 5, indaga quem sao os pais do indivduo bob.
Isso ocorre pois X e uma variavel, assim, o motor de inferencia realiza substituicoes
sobre esta para encontrar quais fatos da base satisfazem a indagacao.
O mesmo ocorre na consulta rotulada com o n
umero 6, porem, esta indaga quais
sao os filhos do indivduo bob.
A consulta rotulada com o n
umero 7 exibe pares genitor-filho, observe que o ordem
das respostas e identica a ordem dos fatos inseridos na base.
Os exemplos exibidos ate o momento ilustram consultas simples, e possvel, por
exemplo, realizar consultas sobre fatos que nao estao diretamente descritos.
Diga-se, por exemplo, que se deseja saber quem sao os avos de determinado sujeito,
apesar de nao existir um fato que determine esta relacao pode-se criar uma consulta
que realize esta pergunta.
Para isso, pode-se digitar no prompt de comandos a seguinte pergunta:
1
2
3
4

8 ? g e n i t o r (Y, jim ) , g e n i t o r (X,Y) .


Y = pat
X = bob ;
No

A consulta pode ser interpretada como Y e genitor de jim e X e genitor de Y, ou


seja, essa consulta busca quem e o pai do pai de um sujeito.
Note que as clausulas estao separadas por uma vrgula (,), para o SWI-Prolog a
vrgula representa uma conjuncao, enquanto um ponto e vrgula (;) representa uma
disjuncao.
Entao, uma sequencia de clausulas separadas por vrgula so sera satisfeita se e
somente se todas as clausulas forem satisfeitas.
123

Do mesmo modo, pode-se afirmar que uma sequencia de clausulas separadas por
ponto e vrgula sera satisfeita se ao menos uma clausula for satisfeita.
Um outro conectivo importante e a negacao.
Para exemplificar a utilizacao deste conectivo serao definidos os predicados homem(x)
e mulher(x), significando que x e homem e x e mulher respectivamente.
Assim, sao definidos os fatos:
1
2
3
4
5
6
7

mulher (pam) .
homem( tom ) .
homem( bob ) .
mulher ( l i z ) .
mulher ( pat ) .
mulher ( ann ) .
homem( jim ) .

Apos definidos os fatos, podemos indagar, por exemplo, quem e a mae de bob,
ou seja, deseja-se saber quem e o genitor de bob que e mulher. Para isso, pode-se
digitar no prompt de comandos a seguinte pergunta:
1 9 ? g e n i t o r (X, bob ) , mulher (X) .
2 X = pam ;
3 No

A mesma consulta poderia ser realizada atraves da seguintes pergunta:


1 10 ? g e n i t o r (X, bob ) , not (homem(X) ) .
2 X = pam ;
3 No

Muitas outras consultas podem ser realizadas sobre uma base de fatos, porem, e
muito mais interessante utilizar regras, pois o poder de expressao obtido e muito
maior.
Para exemplificar o uso de regras sera definida a relacao prole(y,x), significando que
y e prole de x.
Esta relacao e a relacao inversa de genitor(x,y), assim, pode-se afirmar que y e
prole de x se x e genitor de y.
Para isso, pode-se criar a seguinte regra na janela de edicao do programa:
1 p r o l e (Y,X) : g e n i t o r (X,Y) .

124

O smbolo :- pode ser lido como se.


A parte da regra a esquerda do smbolo :- e denominada de conclusao (ou cabeca),
ja a parte a direita deste e chamada de condicao (ou corpo).
Assim, para responder a consulta o interpretador Prolog precisa satisfazer parte
condicional da regra, uma vez que nao existem fatos relacionados a prole, para
entao obter uma conclusao.
Para isso, sao utilizadas substituicoes, ate que se satisfaca a parte condicional ou
nao existam mais possibilidades de substituicao.
Consultas sao realizadas sobre regras da mesma maneira como se estas fossem fatos,
por exemplo, para indagar quem e a prole do indivduo tom deve-se digitar no
prompt de comandos a seguinte consulta:
1
2
3
4

11 ? p r o l e (X, tom ) .
X = bob ;
X = liz ;
No

Para satisfazer a regra o interpretador Prolog substituiu a variavel X ate que encontrou o fato genitor(tom, bob), o qual corresponde a parte condicional da regra
prole(Y,X) :- genitor(X,Y), apos as substituicoes adequadas.
Posteriormente foi encontrado o fato genitor(tom, liz), o qual tambem pode satisfazer a regra, nenhuma outra substituicao resultou em sucesso.
Outras relacoes podem ser definidas para o exemplo. Pode-se definir as relacoes
mae(x,y) e avos(x,y), apresentadas anteriormente como consultas.
Para isso, deve-se digitar na janela de edicao do programa as seguintes regras:
1 mae (X,Y) : g e n i t o r (X,Y) , mulher (X) .
2 avos (X, Z ) : g e n i t o r (X,Y) , g e n i t o r (Y, Z ) .

Um outra relacao que pode ser definida e a relacao irma(x,y), significando x e irma
de y.
Note que deve-se ter cuidado na definicao deste relacionamento, pois, pode-se definir
este atraves da seguinte regra:
1 irma (X,Y) : g e n i t o r (Z ,X) , g e n i t o r (Z ,Y) , mulher (X) .

Porem, esta regra permite uma pessoa seja irma de si mesma, para comprovar isso
basta realizar a seguinte consulta:
125

1 12 ? irma ( pat , pat ) .


2 Yes

O programador deve estar atento a especificacoes deste tipo, para descrever corretamente a relacao e necessario indicar que x e y precisam ser diferentes, assim a regra
correta seria:
1 irma (X,Y) : g e n i t o r ( Z ,X) , g e n i t o r ( Z ,Y) , mulher (X) , not (X=Y
) .

Uma diferenca basica entre uma regra e um fato e que um fato e sempre uma informacao verdadeira, ja uma regra precisa ser avaliada para que se possa determinar
se esta e verdadeira ou nao.
Regras podem depender diretamente de um fato, como no exemplo anterior ou de
outras regras (inclusive dela mesma).
A recursao e um dos elementos mais importantes da linguagem Prolog, este conceito permite a resolucao de problemas significativamente complexos de maneira
relativamente simples.
A construcao de uma regra recursiva sera apresentada atraves da definicao da relacao
descendente(x,y), significando que y e um descendente de x.
possvel definir esta relacao utilizando a relacao genitor, assim, uma descendencia
E
direta, ou seja, quando x e genitor de y, seria representada com a seguinte regra:
1 d e s c e n d e n t e (X, Z ) : g e n i t o r (X, Z ) .

Para outros casos de descendencia, que nao uma descendencia direta, poderiam ser
utilizadas seguintes regras:
1 d e s c e n d e n t e (X, Z ) : g e n i t o r (X,Y) , g e n i t o r (Y, Z ) .
2 d e s c e n d e n t e (X, Z ) : g e n i t o r (X,Y) , g e n i t o r (Y,W) , g e n i t o r (W, Z ) .
3 . . .

porem, esta solucao seria limitada e trabalhosa. Usando recursao e possvel obter
uma solucao bem mais simples e completa para a relacao de descendencia.
Para isso, e necessario definir a seguinte afirmacao x e um descendente de z se existe
um y, tal que, x seja genitor de y e y seja um descendente de z, a seguinte regra
descreve isso:
1 d e s c e n d e n t e (X, Z ) : g e n i t o r (X,Y) , d e s c e n d e n t e (Y, Z ) .

126

assim, utilizando as duas regras, pode-se descrever a relacao de descendencia de


maneira correta.
As duas regras sao necessarias, pois o uso somente da primeira regra so seria suficiente para casos de descendencia direta (equivalente `a relacao genitor), enquanto o
uso exclusivo da segunda regra levaria a uma busca infinita de descendencia.
Entao, a regra Prolog para relacao de descendencia incorpora as seguintes regras:
1 d e s c e n d e n t e (X, Z ) : g e n i t o r (X, Z ) .
2 d e s c e n d e n t e (X, Z ) : g e n i t o r (X,Y) , d e s c e n d e n t e (Y, Z ) .

Pode-se entao realizar consultas na base de exemplo sobre a relacao de descendencia,


para isso, basta escrever a seguinte consulta:
1
2
3
4
5
6

13 ? d e s c e n d e n t e (pam ,X) .
X = bob ;
X = ann ;
X = pat ;
X = jim ;
No

com isso sao exibidos os nomes de todos os descendentes de pam.


Uma consulta Prolog e sempre um conjunto de metas.
Quando uma pergunta e feita, Prolog precisa satisfazer todas as metas. Isso, como
ja afirmado, e equivalente a provar um teorema ou realizar uma deducao.
Para isso, Prolog busca verificar se a(s) meta(s) sao consequencias logicas dos fatos
e regras contidos no programa.
Para ilustrar o procedimento executado para responder uma consulta sera utilizado
o programa de exemplo, relacionado a` relacionamentos familiares.
Diga-se que a consulta ?- descendente(tom, pat). seja realizada. Sabe-se que descendente(bob, pat) e um fato existente no programa.
Este fato seria derivado a partir da primeira regra relacionada a descendencia.
Alem disso, sabe-se que a regra genitor(tom, bob) e um fato.
Com isso, e o fato derivado descendente(bob, pat) pode-se concluir descendente(tom,
pat) utilizando a segunda regra relacionada a` descendencia existente no programa.
Isso ilustra o que foi utilizado pra realizar a prova, sera apresentado agora como esta
prova foi obtida.
127

Assim, feita a pergunta descendente(tom, pat), Prolog procede da seguinte maneira.


Para satisfazer esta meta e procurada alguma clausula (fato ou regra) da qual a
meta possa ser deduzida.
Entao, sao encontradas as duas regras relacionadas `a descendencia existentes no
programa, pois, a cabeca da regra corresponde `a meta.
Como a regra descendente(X,Z) :- genitor(X,Z). aparece primeiro, e como a meta
atual e descendente(tom, pat), as variaveis sao substitudas, tal como a seguir:
1 X = tom , Z = pat

a meta descendente(tom, pat) e substituda pela meta genitor(tom, pat).


Como nao existe uma clausula onde a cabeca seja correspondente a esta, Prolog
realiza um encaminhamento para tras, ou seja, retorna a meta original para tentar
encontrar uma maneira alternativa de satisfaze-la.
Entao, a regra descendente(X,Z) :- genitor(X,Y), descendente(Y,Z). sera utilizada.
Mais uma vez, as variaveis X e Z serao substitudas por tom e pat respectivamente,
porem Y ainda nao foi substituda.
Assim, a meta atual da lugar as metas genitor(tom,Y), descendente(Y, pat). Prolog
deve agora satisfazer a conjuncao de metas genitor(tom,Y), descendente(Y, pat),
isso e feito na ordem em que as metas estao escritas, ou seja, primeiramente Prolog
ira tentar satisfazer genitor(tom,Y).
Realizando uma busca por clausulas que satisfacam a meta, Prolog encontra o fato
genitor(tom, bob), assim, Y e substituda por bob.
Com isso, a meta atual e descendente(bob, pat).
Para satisfazer esta meta a regra descendente(X,Z) :- genitor(X,Z) sera usada novamente.
Observe que esta e uma nova sequencia de prova, sendo assim, as substituicoes
anteriores nao possuem nenhuma relacao com esta.
Com isso, Prolog usa um novo conjunto de variaveis, e assim, pode-se reescrever a
regra como descendente(X1,Z1) :- genitor(X1,Z1).
Assim, como a cabeca deve corresponder meta, as seguintes substituicoes sao realizadas:
128

1 X = bob , Z = pat

e entao, a meta atual e trocada para nova meta genitor(bob, pat). Como esta e
satisfeita por um fato presente no programa o procedimento acaba.

10.4

Listas

Listas sao um dos tipos de dados mais u


teis existentes na linguagem Prolog, diz-se
que uma lista e uma sequencia ordenada de uma quantidade qualquer de elementos.
Os elementos de uma lista podem ser de qualquer tipo, tais como, n
umeros ou
atomos.
Os elementos contidos em uma lista devem ser separados por vrgulas, e precisam
estar entre colchetes.
Por exemplo, uma lista pode conter os nomes dos indivduos do exemplo da secao
anterior, esta lista seria definida como:
1 [ pam , l i z , pat , ann , tom , bob , jim ]

Existem dois tipos de listas, as listas vazias e as nao vazias.


Uma lista vazia e representada por [ ].
Listas nao vazias podem ser divididas em duas partes, sao elas:
cabeca - corresponde ao primeiro elemento da lista;
cauda - corresponde aos elementos restantes da lista.
Por exemplo, para a lista:
1 [ pam , l i z , pat , ann , tom , bob , jim ]

pam e a cabeca, enquanto [liz, pat, ann, tom, bob, jim] e a cauda. Observe que a
cauda e uma nova lista, que por sua vez tambem possui cabeca e cauda.
Assim, pode-se dizer que o u
ltimo elemento de uma lista possui uma cauda vazia
(uma lista vazia).
Pode-se especificar que um elemento de uma lista e tambem uma lista, assim, podese representar listas tais como:

129

1 Hobbies1 = [ t e n i s , musica ] . Hobbies2 = [ sky , comida ] . L i s t a = [ ann ,


Hobbies1 , tom , Hobbies2 ] .

possvel separar as partes de uma lista utilizando uma barra vertical, assim, pode E
se escrever Lista = [cabeca | cauda]. Com isso, e possvel determinar as seguintes
listas:
1 [a | b, c] = [a, b, c]

possvel realizar uma serie de operacoes sobre listas, as secoes seguintes exibem
E
algumas destas acoes.
Para se checar se um determinado elemento pertence `a uma lista deve-se utilizar a
relacao member(x,y), que indica se x pertence a` y, por exemplo:
1
2
3
4
5
6

3 ? member ( a , [ a , b , c ] ) .
Yes
4 ? member ( a , [ [ a , b ] , c ] ) .
No
5 ? member ( [ a , b ] , [ [ a , b ] , c ] ) .
Yes

na consulta rotulada com o n


umero 3, a e um elemento da lista, uma vez que
corresponde `a cabeca desta.
Ja a consulta rotulada com o n
umero 4, indica que a nao pertence a` lista, isso ocorre
por que o elemento contido na lista e uma outra lista [a,b] e nao o atomo a.
Isso e ilustrado na consulta rotulada com o n
umero 5.
Para realizar a concatenacao de listas pode-se utilizar o predicado append(L1,L2,L3).,
este predicado concatena a lista L1 e L2 exibindo o resultado em L3. O Mesmo predicado pode ser utilizado para decompor listas. Para definir a relacao de concatenacao,
e necessario satisfazer as seguintes restricoes:
um argumento e uma lista vazia - caso algum argumento seja vazio a concatenacao resultara na repeticao do argumento nao vazio;
nenhum dos argumentos e vazio - a concatenacao resulta na adicao de todos os
elementos da segunda lista ao final da primeira lista.
Visto isso, tem-se os exemplos:
1 conc ( [ a , b ] , [ ] , [ a , b ] ) = true
2 conc ( [ a , b ] , [ c , d ] , [ a , b , c , d ] ) = true

130

Para definir esta relacao e pode-se implementar as seguintes regras:


1 conc ( [ ] , L , L) .
2 conc ( [ X| L1 ] , L2 , [X| L3 ] ) : conc ( L1 , L2 , L3 ) .

Definidas as regras, pode-se realizar as seguintes consultas:


1
2
3
4
5

6 ? conc
L = [ a ,
No
7 ? conc
L = [ a ,

( [ a , b ] , [ c ] , L ).
b , c ] ;
( [ a ] , [ b , c ] , L ).
b , c ] ;

As regras definidas tambem podem ser usadas para decompor uma lista em suas
componentes. Para checar isso, basta realizar a seguinte consulta:
1
2
3
4
5
6

6 ?
L1 =
L1 =
L1 =
L1 =
No

conc ( L1 , L2 , [ a , b , c ] ) .
[ ] L2 = [ a , b , c ] ;
[ a ] L2 = [ b , c ] ;
[ a , b ] L2 = [ c ] ;
[ a , b , c ] L2 = [ ] ;

A adicao de um elemento `a uma lista pode ser definida de modo simples. Para isso,
basta inserir o elemento no incio da lista, esta relacao e definida atraves da seguinte
regra:
1 i n s e r e (X, L , [X | L ] ) .

com isso, pode-se realizar as seguintes insercoes:


1
2
3
4
5
6

7 ?
L = [
No
8 ?
L = [
No

insere ( a , [ b , c ] , L ) .
a , b , c ];
insere ( [ 1 , 2 ] , 3 , L ) .
[ 1 , 2 ] | 3 ];

A exclusao de um elemento pode ser implementada atraves das seguintes regras:


1 e x c l u i (X, [X | T a i l ] , T a i l ) .
2 e x c l u i (X, [Y | T a i l ] , [Y | T a i l 1 ] ) : e x c l u i (X, T a i l , T a i l 1 ) .

a primeira regra e utilizada quando o elemento que se deseja excluir corresponde a`


cabeca da lista.

131

Ja a segunda regra exclui um elemento que pertence a cauda da lista. Vale salientar que esta implementacao nao exclui todos os elementos existentes na lista que
correspondam ao elemento passado como argumento.
Definidas as regras podem ser realizadas as seguintes consultas:
1
2
3
4
5
6
7
8
9
10

9 ? e x c l u i ( a , [
L = [ b , c ] ;
No
10 ? e x c l u i ( b , [
L = [ a , c ] ;
No
11 ? e x c l u i ( c ,
L = [ a , b , c ]
L = [ a , c , b ]
No

a , b , c ] , L ).

a , b , c ] , L ).

[ a , c , b , c ] , L ).
;
;

Existem diversas outras operacoes nativas de Prolog.

10.5

Aritm
etica

Geralmente, quando se escreve uma expressao matematica a notacao infixa e utilizada, por exemplo 2 a + b c, onde 2, a, b e c sao argumentos e + e sao
operadores.
Em Prolog uma expressao e representada internamente como uma arvore.
Uma maneira de representar em Prolog a expressao em questao utiliza notacao
prefixa, a expressao seria representada como +((2, a), (b, c)).
Porem, por ser mais usual a representacao infixa tambem e compreendida pela
linguagem.
Prolog possui definidos operadores para as quatro operacoes: +, , , /, para
realiza soma, subtracao, multiplicacao e divisao, respectivamente. Para se obter o
resultado de uma operacao e necessario utilizar o operador is, tal como ilustrado
nas consultas abaixo:
1
2
3
4
5
6
7

3 ? X i s 2 + 3 .
X = 5;
No
4 ? X i s 4 1 .
X = 3;
No
5 ? X i s 2 5 .

132

8
9
10
11
12
13

19
X = 10;
No
6 ? X i s 9 / 2 .
X = 4.5;
No

Para o SWI-Prolog a operacao / representa uma divisao real, para se obter uma
divisao inteira deve-se usar o operador //.
A precedencia de operacoes aritmeticas em Prolog e a mesma precedencia adotada na
matematica, assim, quando necessario devem ser utilizados parenteses para descrever
uma expressao corretamente.
Alguns dos operadores reconhecidos sao:
mod - para obter o resto da divisao;
b - para potenciacao;
cos - funcao cosseno;
sin - funcao seno;
tan - funcao tangente;
exp - exponencial;
ln - logaritmo natural;
log - logaritmo;
sqrt - raiz quadrada.
Existem tambem operacoes de conversao, algumas sao automaticas outras precisam
ser explicitamente solicitadas.
Um exemplo de conversao automatica ocorre quando um n
umero inteiro e relacionado em uma expressao com n
umeros de ponto flutuante, automaticamente os
inteiros sao convertidos para n
umeros de ponto flutuante.
Algumas conversoes explcitas nativas sao:
integer(X) - converte X para inteiro;
float(X) - converte X para ponto flutuante.
Prolog tambem possui operacoes para comparacao, os operadores sao:
> maior que;
133

< menor que;


>= maior ou igual;
=< menor ou igual;
=:= igual;
= / = diferente.
importante explicitar a diferenca entre os operadores = e =:=, o primeiro operador
E
verifica se dois objetos sao iguais, enquanto o segundo verifica se o resultado da
operacao e igual.
Isso fica mais claro atraves das consultas:
1
2
3
4
5
6
7
8

18 ? 1 + 2 = 2 + 1 .
No
19 ? 1 + 2 =:= 2 + 1 .
Yes
20 ? 1 + A = B + 2 .
A = 2
B = 1;
No

10.6

Corte de fluxo

A ordem das clausulas em um programa e a ordem de definicao de uma regra podem


determinar o fluxo de execucao de um programa.
O elemento para controle do fluxo denominado de corte, representado por !.
A principal funcao do corte e melhorar a eficiencia de um programa.
Como ja foi apresentado, Prolog utiliza encadeamento para tras sempre que necessario
para satisfazer uma meta.
Porem, muitas vezes, a utilizacao de encadeamento para tras causa uma busca
desnecessaria, levando a ineficiencia.
Para estes casos o uso do mecanismo de corte e extremamente u
til.
Para apresentar o corte sera utilizado o seguinte exemplo: Sejam dados dois n
umeros
X e Y , e desejado saber qual e o valor maximo destes.
As seguintes regras descrevem a relacao maximo(x,y), significando quex e o maximo
valor se x e maior ou igual a y, y e o maior valor se y e maior que x:
134

1 maximo (X, Y,X) : X >= Y.


2 maximo (X, Y,Y) : X < Y.

estas regras computam a relacao de maneira correta, porem, elas sao exclusivas, ou
seja, quando a primeira obtem sucesso a segunda ira falhar.
Porem, Prolog sempre executa as duas regras, utilizando encadeamento para tras,
o que para esta relacao resulta apenas em ineficiencia.
A mesma relacao pode ser obtida, porem, sem gerar processamento ineficiente utilizando corte. Para isso, as regras seriam escritas como:
1 maximo (X, Y,X) : X >= Y, ! .
2 maximo (X, Y,Y) : X < Y.

com isso, caso a primeira regra obtenha sucesso a segunda regra nao sera executada.
A execucao do programa com corte nao altera o resultado deste, o corte apenas evita
que sejam realizadas buscas desnecessarias.
No entanto, o uso do corte exige muito mais atencao do programador.
Isso ocorre pois um programa sem cortes pode ter a ordem de suas clausulas e regras
modificadas sem alterar o significado do mesmo.
Por sua vez, um programa que possua cortes pode ter seu significado alterado caso
suas clausulas sejam reordenadas.
O seguinte exemplo ilustra estas afirmacoes. Sejam dadas as regras:
1 p : a , b .
2 p : c .

o significado logico das regras pode ser interpretado pela formula:

p (a b) c.

(10.8)

Com esta formula podemos modificar a ordem das clausulas e seu significado nao
sera alterado.
Porem, caso seja utilizado corte, tal como nas seguintes regras:
1 p : a , ! , b .
2 p : c .

135

o significado logico das regras pode ser interpretado pela formula:

p (a b) (a c).

(10.9)

Com esta, caso a ordem das regras seja alterada para:


1 p : c .
2 p : a , ! , b .

o significado logico das regras pode sera pela formula:

p c (a b).

(10.10)

Assim, pode-se afirmar que o corte e um mecanismo u


til, porem, deve ser utilizado
com cuidado.

136

Refer
encias Bibliogr
aficas
[1] SCOTT, M. L. Programming Languages Pragmatics. 3. ed. United States: Elsevier,
2009. 941 p.

137

Vous aimerez peut-être aussi