Paradigmas de Linguagens de...

80
Prof. Kesede R Julio Apostila de Paradigmas de Linguagens de Programação versão: 2011

Transcript of Paradigmas de Linguagens de...

Prof. Kesede R Julio

Apostila de

Paradigmas de

Linguagens de Programação

versão: 2011

Prof. Kesede R Julio

Índice

Capítulo 1 Introdução......................................................................................................4

1.1 Recomendações ao Aluno 4

1.2 Alguns Aspectos Básicos 4

1.3 Exercícios 9

Capítulo 2 Paradigma Imperativo....................................................................................9

2.1 Tipos 9

2.1.1 Tipos Primitivos 10

2.1.1 Tipos Compostos 12

2.1.1 Tipos Recursivos 13

2.1 Expressões 14

2.1.1 Operadores Sobrecarregados 17

2.1.2 Erros em expressões 18

2.1.3 Expressões Relacionais 18

2.1.4 Expressões Booleanas 19

2.1.5 Avaliação Curto-Circuito 19

2.2 Comandos 20

2.2.1 Instruções de Atribuição 21

2.2.1 Instruções Compostas e Blocos 22

2.2.2 Condicionais 23

2.2.3 Iterativos 24

2.2.4 Desvio Incondicional 25

2.3 Abstrações 26

2.3.1 Abstração de Processos 26

2.3.1.1 Funções 27

2.3.1.1 Procedimentos 28

2.3.1.2 Parâmetros 28

2.4 Exemplo de Linguagem: Pascal 30

Capítulo 3 Paradigma Orientado à Objeto.....................................................................33

3.1 Tipo abstrato de dados 33

3.2 Herança 39

3.3 Acoplamento dinâmico 41

3.4 Polimorfismo 41

3.5 Exemplo de linguagem: Python 42

3.5.1 Características 42

Paradigmas de Linguagens de Programação - versão 2011 2

Prof. Kesede R Julio

3.5.2 Elementos da linguagem 43

Capítulo 4 Paradigma Lógico.........................................................................................50

4.1Proposições 50

4.2Forma Clausal 51

4.3Cálculo de Predicados e Demonstração de Teoremas 52

4.4Elementos básicos do Prolog 54

4.5Exemplos de Linguagem: Prolog63

Capítulo 5 Paradigma Funcional...................................................................................69

5.1 Fundamentos 69

5.2 Funções Simples 71

5.3 Forma Funcional 71

5.4 A Linguagem Scheme 72

5.4.1 Funções primitivas 72

5.4.2 Funções que constroem funções 74

5.4.3 Funções de Predicados 75

5.4.4 Fluxo de Controle 76

5.4.5 Exemplos 78

Paradigmas de Linguagens de Programação - versão 2011 3

Prof. Kesede R Julio

1 Introdução

1.1 Recomendações ao Aluno

O estudo dos paradigmas de linguagens de programação

requer dedicação na pesquisa de novas linguagens, e sempre

formalizando este estudo. O conhecimento dos diferentes paradigmas

é adquirido através do estudo dos princípios e conceitos que norteiam a

construção das linguagens. Este tipo de pesquisa dará a você condições

de definir com maior propriedade qual dos paradigmas utilizar para

determinada aplicação.

1.2 Alguns Aspectos Básicos

Programa:- Podemos definir um programa como uma

máquina abstrata, pois o mesmo produz e manipula entidades

abstratas (dados). Enquanto documento ele se torna a descrição da

Paradigmas de Linguagens de Programação - versão 2011 4

Prof. Kesede R Julio

máquina, passando a ser a própria máquina quando em execução. O

meio físico onde esta máquina é implementada é o computador.

Linguagem de Programação:- Um conjunto de recursos e

regras capaz de construir máquinas abstratas a serem implementadas

com qualidade em computadores. A primeira linguagem a ser

construída chamava-se Plankalkul, desenvolvida em 1945, publicada

em 1972, porém nunca implementada. Também em meados de 1950,

deu-se o interesse pela Inteligência Artificial, e com ele uma linguagem

que pudesse expressar seus problemas e retornar resultados. Uma

linguagem que pudesse representar dados simbólicos em listas

encadeadas, pois nesta época, as representações eram de dados

numéricos em matrizes. Após varias pesquisas e implementações, o

Lisp foi criado.

As principais características de uma linguagem de

programação, são:

Requisitos:- qual universo de problemas queremos resolver?

Expressividade:- melhor forma para representar os elementos

da linguagem

Paradigma:- qual a forma mais adequada para representar e

resolver os problemas apresentados por uma determinada aplicação.

Implementação:- o que é passível de implementação

Eficiência:- relação entre custo x benefício da implementação

Sintaxe:- como escrevemos os elementos da linguagem.

Semântica:- o significado de cada elemento da linguagem, ou

seja, seu comportamento quando em execução.

Compiladores e Interpretadores:- Para colocarmos em

funcionamento uma linguagem, precisamos de um processador de

linguagem (compilador ou interpretador). Um processador de

linguagem é um programa capaz de transformar códigos escritos pelo

Paradigmas de Linguagens de Programação - versão 2011 5

Prof. Kesede R Julio

programador (programa-fonte) em códigos entendidos pelo sistema

operacional para qual o código foi escrito. Existem ainda alguns

compiladores que geram códigos para serem lidos por máquinas

virtuais, deixando assim o programa-fonte independente dos sistemas

operacionais. Um exemplo disto é o MVJ : Máquina Virtual Java.

Compiladores:- Estes processadores transformam o código

escrito pelo programador em um código que pode ser lido diretamente

pelo computador (código de máquina). Uma vez convertido, o

computador executa, de uma única vez, todo o código de máquina.

Estes processadores tem como principal característica a velocidade de

processamento, porém nada é executado se for verificado algum erro

de sintaxe. A figura abaixo mostra isso com mais detalhes.

Paradigmas de Linguagens de Programação - versão 2011 6

Programa fonte

Analisador Léxico:constroem os símbolos léxicos (lexical tokens) que são os identificadores, palavras reservadas, operadores etc,

para serem analisados sintaticamente

Analisador Sintático (ou parser)verifica se símbolos léxicos obedecem as

regras sintáticas da construção da linguagem, construindo assim uma Árvore Sintática da Estrutura

Analisador Semânticoverifica se há conflitos difíceis/impossíveis de

serem resolvidos pelo Parser. Ex.: coersão de tipos

Gerador de código de máquinaconstroe um código capaz de ser interpretado pela

CPU do computador (micro-instruções)

Prof. Kesede R Julio

Separado do compilador, temos o linker, que tem a tarefa de

juntar arquivos pré-compilados (do sistema operacional ou do usuário)

afim de gerar um único executável.

Interpretadores:- Já nestes processadores, os códigos

escritos pelo programador é verificado e executado um-a-um, ou seja,

cada linha é analítica e sintaticamente depurada e imediatamente

executada pela CPU, caso não se verifique erros. Por isso, programas

feitos nestes processadores podem executar perfeitamente algumas

instruções e interromper a execução logo que encontrar um erro. Sua

grande desvantagem é a lentidão. A figura abaixo mostra o esquema

de um interpretador.

Metodologia de Programação:- Cada problema requer um

ponto de vista para ser olhado. As linguagens de programação são

construídas para dar “amplitude” neste olhar, e assim, resolver

problemas que antes não puderam ser resolvidos, ou eram resolvidos

precária e paliativamente. Com o passar dos anos vários paradigmas de

programação foram sendo desenvolvidos, todos eles pela necessidade

de resolver problemas que outros paradigmas (olhares) não resolviam.

Alguns deles, são:

Paradigmas de Linguagens de Programação - versão 2011 7

Código de máquina

Programa-fonte

Interpretador

Código executado

Prof. Kesede R Julio

Orientado à Objetos:- Olha para o problema e transforma

as entidades em objetos (características e funções). Ex.: C++,

Smalltalk.

Imperativas:- Prioriza as informações (dados) envolvidas no

problema. Ex.: C, Pascal, Fortran.

Declarativas:- Vê o problema como uma relação das

declarações (objetos, dados e relações). Especializando este paradigma

podemos destacar outros dois: Lógica:- as relações são caracterizadas

pela lógica matemática. Ex.: Prolog, Godel e Funcional:- as relações

são caracterizadas por mapeamentos entre estruturas simbólicas. Ex.:

Lisp, Haskell.

1.3 Exercícios

1.3.1Faça uma pesquisa do histórico das linguagens agrupando-as

Paradigmas de Linguagens de Programação - versão 2011 8

Prof. Kesede R Julio

em seus respectivos paradigmas.

2 Paradigma Imperativo

2.1 Tipos

As informações são elementos fundamentais para o estudo de

linguagens, uma vez que é por elas que os programas são feitos. As

informações são caracterizadas por 3 elementos básicos: variáveis,

tipos e valores.

Variáveis:- Para que as informações sejam guardadas e

utilizadas em partes diferentes do programa, precisamos definir

variáveis, que nada mais são que espaços em memória para alocação

temporária da informação.

Tipos:- Através dos tipos classificamos (agrupamos) as

informações de acordo com sua natureza. As regras para tratamento

das informações são definidas de acordo com esta classificação, pois

caso contrário, teríamos que tratar cada informação individualmente. O

tipo define não apenas a natureza da informação, como também o

domínio (valores possíveis) do espaço onde ela será alocada.

Valores:- São representações de um conceito, ou seja, a

Paradigmas de Linguagens de Programação - versão 2011 9

Prof. Kesede R Julio

representação de um nome de pessoa, de um peso, de uma marca de

carro etc.

Juntamente com os tipos definimos também as operações que

serão realizadas sobre eles. Estudaremos os conceitos matemáticos que

originaram os conceitos de agrupamento e tratamento de tipos.

Existem 3 tipos de dados básicos que devemos analisar: primitivos,

compostos e recursivos.

2.1.1 Tipos Primitivos

Um tipo primitivo é, em si mesmo, atômico, ou seja, não pode

ser subdividido. Podemos citar alguns tipos conhecidos, como: inteiro,

booleano, real, caractere, enumerado.

A definição dos tipo de uma linguagem e a forma que serão

tratados dependerá do domínio de aplicações que a linguagem pretende

suportar. Linguagens criadas para fins científicos (Ex.: Fortran) tem

forte apelo aos tipos numéricos de precisão, já linguagens criadas para

fins comerciais (Ex.: Cobol) tem forte apelo para tipos alfanuméricos.

O Numéricos se dividem basicamente em dois tipos: inteiro e

ponto flutuante.

O nome ponto flutuante é utilizado para distinguir a

representação computacional (limitada pelo hardware) da

representação matemática (infinita).

Exemplo em Pascal:

val_int: integer;

val_real: real;

Os Não-Numéricos, podem ser: Booleanos, caracteres e

Paradigmas de Linguagens de Programação - versão 2011 10

Prof. Kesede R Julio

ponteiros.

Os booleanos permitem a representação dos valores 0 e 1. Já

o tipo caractere permite o agrupamento de símbolos ('A'..'Z', 'a'..'z',

'0'..'9', ':', ';' etc) e o tipo ponteiro agrupa endereços de memória, afim

de suportar endereçamento direto pelas linguagens, além de alocação e

desalocação dinâmica de varáveis.

Exemplo em Pascal:

var

caractere: Character; // variável tipo caractere

booleano: Boolean; // variável tipo boolean

pont_int: ^Integer; // variável tipo ponteiro para inteiro

O tipo Enumerado permite a construção de novos conjuntos

de dados, além daqueles já contemplados pela matemática, mantendo

a ordem com que são declarados. Um valor inteiro é associado a cada

valor da enumeração.

Exemplo em Pascal:

type

Estacoes = (Verao, Outono, Inverno, Primavera);

Pascal e Modula-2 ainda suporta o tipo intervalo, que também

é um subconjunto de um tipo primitivo, e portanto, todas as operações

vinculadas ao tipo é herdada.

Exemplo de Pascal:

type

Meses = 1..12;

2.1.1 Tipos Compostos

Desta forma podemos construir tipos a partir de outros tipos,

Paradigmas de Linguagens de Programação - versão 2011 11

Prof. Kesede R Julio

primitivos ou não. Isto é interessante a partir do momento em que

precisamos agrupar informações diferentes, porem dentro de um

mesmo contexto. Exemplo: (datas: dia, mês e ano; coordenadas de um

ponto: x e y; dias possíveis para pagamento de uma conta etc). Assim

como os tipos primitivos, os tipos compostos também são provenientes

das leis matemática: produto cartesiano, união disjunta, mapeamentos

e conjunto potência.

Exemplo em C de Produto Cartesiano:

enum DiasC{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}

enum MesesC{jan,fev,mar,abr,mai,jun,

jul,ago,set,out,nov,dez}

struct DataC{

DiasC d;

MeseC m;

};

Exemplo em C de União Disjunta:

union NumeroC{

int ival;

float rval;

};

Neste exemplo, a variável ival e rval compartilharão do

mesmo espaço de memória, ou seja, quando um valor é atribuído à

ival, o valor de rval é sobreposto.

Em programação, os Mapeamentos são feitos através da

estrutura de vetores, onde o conjunto domínio é denominado de índice

e o conjunto imagem é o elemento do vetor.

Paradigmas de Linguagens de Programação - versão 2011 12

Prof. Kesede R Julio

Exemplo em Pascal:

Var

tempP:array[1..30] of real;

As funções também podem ser consideradas como

mapeamento, uma vez que o domínio seria os argumentos de entrada

e a imagem o retorno da função.

Exemplo em Pascal de Conjunto Potência:

type

Cores = (vermelho, azul, amarelo);

NovasCores = set of Cores;

Neste exemplo o tipo NovasCores tem os seguinte valores:

{{}, {vermelho}, {azul}, {amarelo}, {vermelho, azul}, {vermelho,

amarelo}, {azul, amarelo},{vermelho, azul, amarelo}}

A linguagem C não contempla este tipo de conjunto.

2.1.1 Tipos Recursivos

Um tipo recursivo contém elementos do seu próprio tipo. A

cardinalidade do conjunto resultante é infinita.

Exemplo em Pascal:

type

IntListP = ^IntNode;

IntNode = record

valor: Integer;

prox: IntListP

end;

Paradigmas de Linguagens de Programação - versão 2011 13

Prof. Kesede R Julio

2.1 Expressões

Podemos representar valores através de expressões

aritméticas. A grande maioria das linguagens provê uma série de

elementos capazes de manipular variáveis oferecendo os mesmos

recursos da matemática através de operadores, operandos, parênteses

e chamadas de função. Os operadores podem ser unários, binários ou

até mesmo ternários (C, C++ e Java). No caso dos operadores

binários, a maioria deles tem processamento infixo, ou seja, os

operadores são colocados entre os operandos.

O resultado de uma expressão depende de precedência que

a linguagem considera. Por exemplo:

a + b * c

A matemática diz que a multiplicação e a divisão precedem a

soma e subtração, portanto se esta expressão fosse considerar esta

regra, a multiplicação seria executada antes da soma.

5 + 3 * 2 = 11

Caso a avaliação seja feita da esquerda para direita, o

resultado será 16.

Abaixo temos uma tabela de precedência de algumas

linguagens imperativas:

Fortran Pascal C Ada

Mais alta ** *, /, div, mod ++, -- (pós) **, abs

*, / todos +, - ++, -- (pré) *, /, mod

todos +, - +, - (unário) +, - (unário)

*, /, % +, - (binário)

Mais baixa +, - (binário)

Quando temos uma seqüência de operadores com mesma

Paradigmas de Linguagens de Programação - versão 2011 14

Prof. Kesede R Julio

precedência, o ordem de avaliação será imposta pela regra de

associatividade. Na maioria das linguagens, esta regra diz que a

avaliação será feita da esquerda para a direita. Em Fortran a

exponenciação é associativa da direita para a esquerda.

5 ** 1 ** 2 =

= 5 ** 1 = 5

As regras de precedência e associatividade podem ser

modificadas através de parênteses. Ex. Em Pascal:

(A + B) * C

Algumas linguagens também permitem o uso de operadores

condicionais. Um exemplo disso é o operador ternário ?:, usado em C,

C++ e Java. Por exemplo, em C podemos escrever:

if (cont==0){

media=0;

}

else{

media=soma/cont;

}

Ou:

media=(cont==0)?0:soma/cont;

A expressão (cont==0) antes do “?” é avaliada, caso seja

verdadeira, toda a expressão valerá “0”; caso contrário será

“soma/cont”.

Um estudo importante a ser feito também é o efeito

colateral na ordem de avaliação. Por exemplo, podemos ter efeito

colateral funcional, que acontece quando uma função modifica o valor

Paradigmas de Linguagens de Programação - versão 2011 15

Prof. Kesede R Julio

de seu parâmetro. Considere:

...

a=10;

b=a+fun(a);

...

int fun(&a){

int b=a/2;

a=a*2;

return b;

}

Se a função não modificar o parâmetro não teremos

problema, porem se houver modificação a ordem da avaliação dos

operandos incidirá diretamente no resultado. Por exemplo, vamos

considerar que a função “fun()” retorne o valor do parâmetro dividido

por 2 (5) e modifique o valor do parâmetro para o seu dobro (20). A

ordem de avaliação aqui é muito importante, pois se “a” for trazido da

memória antes da execução de fun(), então a expressão valerá 15; se

fun() for avaliada antes, a expressão valerá 25.

O mesmo acontece com modificações em variáveis globais.

Por exemplo:

...

int a=5;

int func1(){

a=17;

return 3;

}

void func2(){

a=a+func1();

Paradigmas de Linguagens de Programação - versão 2011 16

Prof. Kesede R Julio

}

void main(){

func2();

}

A ordem de avaliação dos operandos, neste caso, também

alterará o resultado de a na função func2(). O resultado poderá ser 8

ou 20.

Pascal e Ada permitem que a avaliação seja feita em qualquer

ordem, definida pelo implementador, e portanto, estas linguagens

estão sujeitas a efeitos colaterais. Java evita estes efeitos definindo que

a avaliação será feita da esquerda para direita.

2.1.1 Operadores Sobrecarregados

Um dos problemas de sobrecarga de operador é referente a

legibilidade. Outro problema é o erro causado pelo esquecimento de um

dos operandos tornando a semântica da expressão completamente

diferente. Exemplo do & da linguagem C:

c = a&b;

A disponibilização de operadores diferentes para operações

diferentes facilita a leitura e a depuração do código. Um exemplo

clássico é o operador “/”. Considere a divisão de dois operandos

inteiros (“a” e “b”) retornando o resultado em uma variável real (“c”),

em C e em Pascal.

Em C : c=a/b;

Em Pascal : c:=a/b;

No caso de C, o retorno é a parte inteira do resultado real

truncado e no caso do Pascal o retorno é real, pois existe um operador

especificamente para inteiros (“div”).

Paradigmas de Linguagens de Programação - versão 2011 17

Prof. Kesede R Julio

Além dos operadores sobrecarregados pré-definidos, existem

linguagens (Ada, C++) que permitem que os programadores possam

sobrecarregar ainda mais os operadores.

A sobrecarga deve sempre ser usada com critérios, pois nada

impede que o usuário defina um “*” como operador de soma.

2.1.2 Erros em expressões

Os erros mais comuns que encontramos em expressões são

erros de “overflow” (resultado da expressão não consegue ser

representado na célula de memória reservada para seu

armazenamento) e divisão por zero. Estes erros podem ser chamados

também de “exceções”, podendo assim ser tratados pelo programador.

2.1.3 Expressões Relacionais

As expressões relacionais são constituídas de operadores

relacionais (maior, menor, maior ou igual, menor ou igual, diferente,

igual) e seus operandos. O valor resultante de uma expressão

relacionais é, geralmente, booleana. Os operadores relacionais têm

sempre menor precedência que os aritméticos, assim:

a+1>2*b

as operações aritméticas de cada lado do operador maior “>”,

serão executadas primeiro.

2.1.4 Expressões Booleanas

Expressões booleanas são formadas por operadores booleanos

(not, and e or, nesta ordem de precedência na maioria das linguagens

imperativas) e operandos que podem ser: expressões relacionais,

variáveis e constantes.

Aqui os operadores se misturam, e portanto precisamos de

uma ordem de avaliação destas relações.

Paradigmas de Linguagens de Programação - versão 2011 18

Prof. Kesede R Julio

Mais alta

Mais baixa

**, abs, not

*, /, mod, rem

+, - (unários)

+, -, & (binários)

=, /=, <, >, <=, >=, in, not in

And, or, xor, and then, or else

Em C, como não existe o tipo booleano, o retorno da

avaliação da expressão booleana é 0 (zero) quando falso e 1 (um)

quando verdadeiro. Uma situação interessante em C é a validação da

seguinte expressão:

a>b>c

como os operadores relacionais em C são associativos à

esquerda, “a>b” é avaliado e o resultado é comparado com “c”.

Em linguagem com tipos booleanos, os operandos das

expressões booleanas também devem ser booleanos. Em Pascal, os

operadores booleanos tem precedência sobre os operadores relacionais,

e portanto está incorreta a expressão:

a > 5 or a < 0

pois 5 não é um operando booleano. O correto seria:

(a > 5) or (a < 0)

2.1.5 Avaliação Curto-Circuito

Usamos este termo quando o resultado de uma expressão é

determinado sem avaliar todos os operandos e/ou operadores.

Exemplo:

(a>=0) and (b<10)

se a for menor que zero, a expressão relacional “b<10” não é

avaliada, pois “false and x = false”, independente do valor de x.

Suponha a seguinte expressão em C:

Paradigmas de Linguagens de Programação - versão 2011 19

Prof. Kesede R Julio

(a>b) || (b++ / 3)

neste caso, b só será modificado se “a>b= false”.

Em Ada a avaliação pode ser determinada pelo programador,

então:

indice:=1;

while (indice <= limite) and then (lista(indice) /= chave)

loop

indice := indice + 1;

end loop;

Caso índice for maior que limite, não teremos a avaliação da

expressão relacional “lista(índice)/=chave”, e assim evitamos o erro.

Em C, os operadores && e || são curto circuitos, porem & e |

bit a bit, que podem ser usados em expressões booleanas, não são

curto-circuitos.

2.2 Comandos

Assim como as expressões respondem pela transformação dos

dados, os comandos são responsáveis pela mudança de estado

do conteúdo das variáveis. Podemos dividí-los em dois grandes

grupos: aqueles que mudam o estado das variáveis, propriamente

dito e aqueles que controlam o fluxo da execução do programa,

ditando quais serão as próximas instruções a serem executadas.

2.2.1 Instruções de Atribuição

Através delas podemos modificar dinamicamente as

vinculações de valores a variáveis. Existem várias formas de utilizarmos

atribuição, são elas:

Atribuição simples:- A forma mais simples de atribuição é:

Paradigmas de Linguagens de Programação - versão 2011 20

Prof. Kesede R Julio

<variável alvo> <op de atrib> <expressão>

Linguagens que usam “=” para atribuição pode causar

confusão com a igualdade condicional. Por exemplo, em PL/I

A=B=C

o retorno da expressão condicional “B=C” é atribuído para

“A”. O ideal é usar um símbolo diferentes para os diferentes propósitos

(:= do Algol, primeira linguagem a usar este símbolo para atribuição).

Alvos Múltiplos:- Podemos definir vários alvos para o

resultado de uma expressão. Em PL/I:

SOMA, TOTAL=0

Alvos Condicionais:- O alvo depende de uma condição.

Exemplo em C++:

bandeira? Cont1=0 : cont2 = 0;

equivale a

if (bandeira) cont1=0; else cont2=0;

Operadores de Atribuição Compostos:- uma forma de

abreviar a instrução de atribuição em algumas linguagens (Algol, C, C+

+, Java). Exemplo em C:

soma += valor;

equivale a

soma=soma+valor;

Operadores de Atribuição Unários:- As linguagens C, C++

e Java oferecem operadores aritméticos para incremento (++) e

decremento (--) posfixados (a++) ou prefixados (++a). Exemplos em

C:

• soma = ++cont;

Paradigmas de Linguagens de Programação - versão 2011 21

Prof. Kesede R Julio

cont é incrementado em 1 e depois atribuído a soma.

• soma = cont++;

cont é atribuído para soma e depois incrementado em 1.

• cont++;

incrementa 1 na variável cont e atribui o resultado a cont.

Quando dois operadores unários são aplicados em um mesmo

operando existe a associatividade da direita para a esquerda. Exemplo

em C:

-cont++;

cont é incrementado em 1 e depois torna-se negativo.

Um problema comum em C é o operador de atribuição ser

usado ao invés de um operador condicional. Exemplo:

if (x=y) ...

aqui, y é atribuído para x e o valor de x é avaliado, se for 0

(zero) a expressão é falsa e se for diferente de 0 (zero) é avaliada

como verdadeira. Java não permite que isto aconteça rejeitando

expressões não booleanas nas instruções if.

2.2.1 Instruções Compostas e Blocos

Instruções compostas são estruturas da linguagem capaz de

tratar como um único comando, vários comandos. Este tipo de

estruturas (seqüência de instruções) requerem que tenhamos

delimitadores, afim de ser definido onde começa e onde termina a

instrução composta. Delimitador em C, C++ e Java: “{” e “}”; em

Pascal e Algol 60: “begin” e “end”.

Já os blocos de comandos permitem declarações de variáveis

locais em sua estrutura, seguida de uma seqüência de comandos.

Pascal, por exemplo, só permite bloco de comandos definidos em

programas ou sub-rotinas (apenas nos blocos de comandos são

Paradigmas de Linguagens de Programação - versão 2011 22

Prof. Kesede R Julio

permitidos declaração de variáveis), ou seja, não é permitida esta

construção para qualquer outro propósito.

2.2.2 Condicionais

Comandos condicionais são aqueles que permitem que o

programa execute determinada(s) instrução(ões) de acordo com uma

condição, ou seja, podemos selecionar quais instruções devem ser

executadas. Existem dois tipos de seleção que podemos fazer: seleção

bidirecional e seleção n-direcional.

Comandos bidirecionais existem em todas as linguagens de

programação. Exemplo em C:

if (a<5){

b=7;

}

else{

b=10;

}

Nos comandos n-direcionais, uma seqüência de instruções

podem ser seguidas, ao invés de apenas duas. Exemplo em Pascal:

case letra of

'a', 'e': begin

contae:=contae + 1

end;

'i', 'o': begin

contio:=contio + 1

end;

'u' : begin

contu:=contu + 1

end;

Paradigmas de Linguagens de Programação - versão 2011 23

Prof. Kesede R Julio

else begin

contcons := contcons + 1

end

2.2.3 Iterativos

Os comandos iterativos são constituídos basicamente de corpo

do comando e controlador da iteração. No corpo do comando estão as

instruções a serem executadas, dependendo da condição contida no

controlador da iteração. Existem os comandos iterativos com numero

de iterações preestabelecido e os comandos iterativos com numero de

iterações indefinido.

Aqueles que são preestabelecidos, o numero de iterações é

determinado a priori através de uma variável de controle. O numero de

iterações define quantas vezes as instruções contidas no corpo do

comando serão executadas. Sintaxe do comando em Pascal:

for <variável> := <expressão 1> (to/downto) <expressão 2

> do

<corpo do comando>

Aqui, a variável de controle é inicializada pela expressão 1 e é

incrementada de 1 (to) ou decrementada de 1 (downto) até que seja

maior ou menor, respectivamente, que a expressão 2. Enquanto isto

não acontecer, o corpo do comando é executado. A variável de controle

não pode ser modificada no interior do corpo do comando.

Algumas linguagens (C, C++ e Java) permitem a modificação

da variável no corpo do comando, assim como mais de uma variável de

controle. Exemplo em C:

somai=0;

somar=0.0;

for (i=10,r=2.0; (i<100 || r<20.0);i=i+2,r=r*2.5){

Paradigmas de Linguagens de Programação - versão 2011 24

Prof. Kesede R Julio

somai=somai + i;

somar=somar + r;

}

Os comandos com numero de iterações indefinido são

controlados por uma ou mais condição que pode ser avaliada no inicio

ou no final do comando. Em C, Java, Ada, C++, temos o comando

while que avalia a expressão condicional antes da execução das

instruções que fazem parte do corpo do comando. A avaliação pode

também ser feita no final do comando (do-while, repeat-until). Exemplo

em C:

somai=0;

somar=0.0;

i=10;

r=2.0;

while ( i<100 || r<20.0){

somai=somai + i;

somar=somar + r;

i=i+2;

r=r*2.5;

}

Neste exemplo, como em qualquer caso dos comandos

iterativos indefinidos, podemos alterar as variáveis que estão sendo

avaliadas na expressão condicional.

2.2.4 Desvio Incondicional

Este tipo de comando transfere a execução do programa para

qualquer local desejado pelo programador. Esta flexibilidade, porem

torna-se um problema, pois não existe observância alguma das

estruturas já estabelecidas. O Fortran foi a primeira linguagem a utilizar

este comando (goto), no entanto, caiu em desuso devido a indisciplina

Paradigmas de Linguagens de Programação - versão 2011 25

Prof. Kesede R Julio

do comando e a ilegibilidade do código causado por ele. O desvio é feito

para um rótulo que pode estar localizado tanto antes, como depois do

comando. Exemplo em C:

...

a=0;

volta: a=a + 1;

if (a<10){

goto volta;

}

...

Quando a execução alcança o comando goto, ela volta a

executar “a=a+1;”, no entanto, só alcançará o goto se “a<10”. Este

comando é EXTREMAMENTE não recomendado.

2.3 Abstrações

Ao falarmos de abstração pensamos no problema como um

todo e não nos detalhes de sua resolução. No entanto, quem

desenvolve a abstração deverá pensar em como resolver, ao passo

que quem irá utiliza-la deverá pensar apenas o que ela faz. Existem

dois tipos de abstração disponíveis nas linguagens: de processo e de

tipo.

2.3.1 Abstração de Processos

Os processos funcionam como uma caixa-preta, ou seja,

esconde os detalhes de implementação da resolução do problema.

Podemos desenvolver dois tipos de abstração de processos: funções e

procedimentos.

Paradigmas de Linguagens de Programação - versão 2011 26

Prof. Kesede R Julio

2.3.1.1 Funções

Uma função é a representação do mapeamento entre o

domínio e a imagem da função. Para cada conjunto de dados de

entrada (domínio) teremos um dado como saída (imagem).

Podemos encontrar este tipo de abstração em linguagens

imperativas, funcionais e OO.

Exemplo de uma função em Pascal: Cálculo de

exponenciação:

function pot (x:Real;n:Integer):Real;

begin

if n=1 then

pot:=x;

else

pot:=x*pot(x,n-1)

end

Aqui temos o identificador da função “pot”, “x” e “n” são os

parâmetros formais, o ultimo “Real” da primeira linha é o tipo do valor

resultante e o que estiver entre “begin” e “end” é o bloco de comandos

que descreve o comportamento da função, neste caso, o comando

“if-else”. Para retornar o resultado, há uma atribuição para o nome da

função: “pot:=x*pot(x,n-1)” . Neste caso, pot é uma pseudo-variável,

pois a cada nova chamada recursiva uma nova variável é criada e

colocada em uma pilha de solução da função. Para executar esta

função, o usuário deverá escrever “pot(3.0,2)”, caso queria a base 3

elevado a segunda potência. Em C, poderíamos colocar toda a

expressão “x*pot(x,n-1)” no próprio retorno, assim teríamos uma

expressão pura, pois os valores seria guardados diretamente na pilha

gerada pela recursão.

Paradigmas de Linguagens de Programação - versão 2011 27

Prof. Kesede R Julio

2.3.1.1 Procedimentos

Assim como as funções, estas abstrações descrevem um

comportamento computacional. Porém, não resulta em uma imagem a

partir do domínio, como nas funções, mas sim, alteram o estado do

programa através dos argumentos fornecidos a ele. Em C, os

procedimentos são tratados como funções que retornam “void” (nulo).

Um exemplo de sintaxe de um procedimento em Pascal:

procedure P(PF1,...,PFn);

<bloco>

Onde P é o identificador da procedure, PFi são os parâmetros

formais e “<bloco>” é o conjunto de comandos que ditarão o

comportamento do procedimento e mudará o estado do programa

através de efeito colateral.

2.3.1.2 Parâmetros

Chamamos de parâmetros os dados que são enviados para as

funções e procedimentos. A expressividade dos processos estão

diretamente ligados aos seus parâmetros. Exemplo em C:

float pot_restrita(){

float potência;

if (n==1){

potência=x;

}

else{

n=n-1;

potência=x*pot_restrita();

}

return potência;

Paradigmas de Linguagens de Programação - versão 2011 28

Prof. Kesede R Julio

}

...

int n=2;

float x=10;

void main(){

float pot10_2;

...

pot10_2=pot_restrita();

...

}

Neste exemplo, o usuário deverá saber o tipo de variável

declarada internamente na função, pois os valores trabalhados pela

função não são passados por parâmetro e portanto estão “escondidos”.

Quando temos as variáveis definidas como parâmetros,

podemos enviar apenas as informações sem a preocupação de

conhecer como serão estes valores manipulados. O mesmo exemplo

com parâmetros:

float pot(float x, int n){

float potência;

if (n==1){

potência=x;

}

else{

potência=x*pot(x,n-1);

}

return potência;

}

A chamada para esta função seria pot(10,2).

Paradigmas de Linguagens de Programação - versão 2011 29

Prof. Kesede R Julio

Aos parâmetros declarados na abstração chamamos de

parâmetros formais e a expressão associada a cada parâmetro formal

chamamos de parâmetro real ou atual, seus valores são chamados de

argumentos.

2.4 Exemplo de Linguagem: Pascal

Uma das principais linguagens usada para o ensino da

programação é o Pascal. Aqui vamos colocar alguns exemplos da

linguagem para conhecermos a semântica de seus principais

elementos.

Exemplo 1: Programa em que o usuário entra com sua idade e o ano atual e o

programa lhe informa o seu ano de nascimento.

Program AnoNasc;

Var

intIdade:integer;

intAno:integer;

intDataNascimento : integer;

Begin

write( 'Digite sua Idade:' );

readln (intIdade) ;

write( 'Digite o Ano Atual:' );

readln (intAno) ;

intDataNascimento := ( intAno - intIdade);

writeln( 'Voce nasceu em: ', intDataNascimento );

End.

Exemplo 2: Programa que mostra a utilização de ponteiros. Problema. Alterar o valor

armazenado em uma variável usando um ponteiro que aponta para o endereço dessa

Paradigmas de Linguagens de Programação - versão 2011 30

Prof. Kesede R Julio

variável.

Program Ponteiros;

Var a: integer;

p: ^integer;

Begin

a := 8 ; // Guarda o valor 8 em a

p := nil; // O ponteiro não guarda nenhum endereço

writeln( 'Valor armazenado em a: ' , a );

// Guarda no ponteiro o endereço da variável a

p := @a ;

writeln( 'Valor apontado por p: ' , p^ );

// O comando abaixo é equivalente a “a:= 2 * a ;” , pois p

// guarda o endereço de a (p aponta para a)

a:= 2 * p^ ;

writeln( 'O valor de a agora: ' , a ); // Imprime 16

writeln( 'Valor apontado por p: ' , p^ ); // Imprime 16

readln ;

End.

Exemplo 3: Este programa ilustra a alocacao dinamica com ponteiros.

Problema. Alocar memória para um ponteiro, guardar nele um valor, depois colocar

este valor em uma variável.

Program AlocacaoDinamica;

Var p: ^integer;

v : integer ;

Begin

new( p ); // Aloca memória para armazenar um inteiro

p^ := 10 ; // Guarda um inteiro na posição apontada por p

writeln( 'Valor armazenado na posicao de memoria: ', p^ );

v:= p^ ; //Guarda em v o inteiro apontado por p

writeln( 'Valor armazenado em v: ', v );

dispose( p ); // Libera a memoria amarrada a p

readln ;

Paradigmas de Linguagens de Programação - versão 2011 31

Prof. Kesede R Julio

End.

Exemplo 4: Este programa ilustra a utilização de enumeracoes.

Program Enumeracao;

var diasSemana : (domingo, segunda, terca, quarta, quinta, sexta, sabado) ;

Begin

writeln( 'Depois de segunda vem quinta? ' , succ(segunda) = quinta );

writeln( 'Depois de segunda vem terca? ' , succ(segunda) = terca );

writeln( 'Antes de quinta vem quarta? ' , pred(quinta) = quarta );

writeln( 'Antes de quinta vem segunda? ' , pred(quinta) = segunda );

End.

Exemplo 5: Este programa mostra mostra como construir listas lineares usando

ponteiros.

Program ListasLineares;

// Tipo de dados que representa um nó da lista

type TNo = record

dado : integer ; // Dado armazenado no nó

prox : ^TNo ; // Ponteiro para próximo nó

end ;

Var pinicio: ^TNo; // Guarda endereço do 1º nó da lista

p1: ^TNo; // Auxiliar. Guarda endereço de um nó

resposta : char ; // Auxiliar. Controla repetição.

Begin

pinicio := nil ;

// Repetição que monta a lista, adicionando novos nós

repeat

new( p1 );

Paradigmas de Linguagens de Programação - versão 2011 32

Prof. Kesede R Julio

write( 'Entre com novo inteiro: ' );

readln( p1^.dado ) ;

p1^.prox := pinicio ;

pinicio := p1 ;

write( 'Novo dado(S/N)?' );

readln( resposta );

resposta := upcase( resposta );

Until resposta = 'N' ;

// Percorre a lista, imprimindo seus elementos

p1 := pinicio ;

while( p1 <> nil ) do

Begin

writeln( 'Achei: ' , p1^.dado );

p1 := p1^.prox ;

End;

// Percorre a lista, liberando memória alocada para os nós

while( pinicio <> nil ) do

Begin

p1 := pinicio ;

pinicio := pinicio^.prox ;

dispose( p1 );

End;

readln ;

End.

Paradigmas de Linguagens de Programação - versão 2011 33

Prof. Kesede R Julio

3 Paradigma Orientado à Objeto

3.1 Tipo Abstrato de Dados

Chamamos de TAD (Tipo Abstrato de Dados) o agrupamento

de dados (atributos), juntamente com os módulos (métodos) que

manipulam estes dados.

Um TAD pode ser disponibilizado para programas aos quais

eles não foram escritos, à priori. Desta forma, podemos construir

bibliotecas, reunindo (encapsulando) códigos e dados semanticamente

relacionados. Isto permite a reutilização de processos computacionais

já realizados e exaustivamente testados.

Para criarmos bibliotecas, a linguagem deve prover a geração

de unidades de compilação (encapsulamentos compiláveis

separadamente) para que o cliente possa melhor usufruir da

capacidade de reutilização de processos computacionais.

Por exemplo, suponhamos a construção de uma TAD Pilha,

contendo as seguintes operações:

cria(pilha) :- cria um objeto pilha

destroi(pilha) :- desaloca o espaço gerado

vazia(pilha) :- retorna verdadeiro ou falso, caso a pilha

esteja vazia ou não, respectivamente.

empilha(pilha, elemento) :- insere um elemento na pilha

desempilha(pilha) :- retira um elemento da pilha

topo(pilha) :- retorna o topo da pilha

Assim, um cliente que quisesse usar esta TAD, poderia ter o

seguinte código:

...

cria(pilha1);

Paradigmas de Linguagens de Programação - versão 2011 34

Prof. Kesede R Julio

empilha(pilha1, cor1);

empilha(pilha1, cor2);

if (!vazia(pilha1))

temp=topo(pilha1);

Qualquer mudança de implementação da pilha não afetará o

código do cliente, uma vez que o acesso aos métodos é feito através de

sua assinatura.

Exemplos:

SIMULA 67: Uma das primeiras implementações de TAD foi

realizada pelo SIMULA 67, através de classes. Sintaxe:

class <nome da classe>

begin

<declaracões de variáveis da classe>

<definições de sub-programas da classe>

<seção de código da classe>

end <nome_da_classe>

A seção de código é executada apenas uma vez, permitindo a

inicialização das varáveis da classe, por exemplo. Uma restrição desta

linguagem é a não proteção das variáveis da classe.

Ada: Esta linguagem permite a ocultação de suas

representações. O encapsulamento é realizado por um elemento

chamado de pacote. Há dois tipos de pacote: pacote de especificação e

pacote de corpo.

A interface do encapsulamento integra o pacote de

especificação e implementação das interfaces integram o pacote de

Paradigmas de Linguagens de Programação - versão 2011 35

Prof. Kesede R Julio

corpo. Estes pacotes podem ser compilados separadamente, desde que

o pacote de interface seja compilado primeiro.

Podemos tornar um tipo visível, porém não a sua

representação. Assim declaramos o tipo na parte visível do pacote:

type TIPO_VERTICE is private;

Depois repetimos sua declaração completa na parte oculta.

Package TIPO_LISTA_ENCADEADA is

type TIPO_VERTICE is private

private

type TIPO_VERTICE;

type PTR is access TIPO_VERTICE;

type TIPO_VERTICE is record

INFO : INTEGER;

ELEM : PTR;

end record;

end TIPO_LISTA_ENCADEADA;

C++: Os TAD´s em C++ são realizados através da definição

de suas classes. Eles são uma extensão da struct em C. São herdadas

da linguagem SIMULA 67 e suas descrições são tipo de dados.

As unidades de programa que instanciam uma classe tem

acesso as entidades públicas desta classe, através de suas instâncias.

Os dados da classe são chamados de atributos (membro de

dados) e as funções de métodos (funções-membro). Todas as

instâncias da classe compartilham de suas funções-membro, porém

Paradigmas de Linguagens de Programação - versão 2011 36

Prof. Kesede R Julio

cada instância têm acesso ao seu próprio conjunto de atributos.

Podemos definir uma classe apenas com os cabeçalhos das

funções-membro, tendo seu corpo definido no cliente. Isto fará com

que as compilações ocorram separadamente, aliviando o custo

computacional do cliente.

Visibilidade :- As entidades de uma classe podem ser visíveis

ou não. Para isto temos as cláusulas private, public e protected.

Quando a entidade é declarada como private, ela é visível apenas pela

própria classe, quando é protected, é visível pela classe e pela classe

filha (herança) e quando é public, é visível por todos os clientes que

instanciarem um objeto da classe.

Construtor :- Podemos definir um construtor, que é uma

função implicitamente chamada quando da instanciação de um objeto

da classe. Além disso, podemos ter mais de um construtor para uma

mesma classe, obviamente sobrecarregados.

Destrutor :- Podemos definir um destrutor, que é uma função

implicitamente chamada quando da desalocação do objeto instanciado

para a classe.

Tanto construtores, quanto destrutores não têm tipo de

retorno e não podem ser chamados explicitamente (os construtores

podem em algumas situações especiais).

Exemplo Classe: Mostre o uso de classes em C++, através de uma classe pilha.

#include <iostream.h>

#include <conio.h>

class pilha{

private:

int *ptr_pilha;

int tam_max;

int top_ptr;

public:

Paradigmas de Linguagens de Programação - versão 2011 37

Prof. Kesede R Julio

pilha(){

ptr_pilha = new int [100];

tam_max = 99;

top_ptr = -1;

}

~pilha(){

delete [] ptr_pilha;

}

void empilha(int numero){

if (top_ptr == tam_max)

cerr << "a pilha esta cheia";

else

ptr_pilha[++top_ptr] = numero;

}

void desempilha(){

if (top_ptr == -1)

cerr << "a pilha esta vazia";

else

top_ptr--;

}

int topo(){

return ptr_pilha[top_ptr];

}

int vazia(){

return top_ptr == -1;

}

void exibe(){

int i;

if (top_ptr == -1)

cerr << "a pilha esta vazia";

else{

i=0;

while (i <= top_ptr)

cout << ptr_pilha[i++] << endl;

}

}

};

Paradigmas de Linguagens de Programação - versão 2011 38

Prof. Kesede R Julio

int main(){

int top_one;

pilha stk;

stk.empilha(42);

stk.empilha(17);

stk.empilha(10);

stk.empilha(30);

stk.empilha(15);

stk.exibe();

getch();

top_one = stk.topo();

stk.desempilha();

stk.exibe();

getch();

stk.desempilha();

stk.desempilha();

stk.exibe();

getch();

}

3.2 Herança

A herança veio resolver dois principais problemas: reutilização

de uma classe com necessidade de ser modificada para a nova

aplicação e também para organizar os programas.

Herdar uma classe significa estender atributos e/ou funções

de uma classe já existente. Por exemplo, podemos já ter criado uma

classe ponto, contendo como atributo “x” e “y”. Quando formos

construir a classe circunferência, podemos declarar apenas o atributo

“raio”, herdando os atributos “x” e “y” do centro da circunferência a

partir da classe ponto.

Chamamos de classe derivada (subclasse), a classe que

herda entidades de outra. Já a classe de onde as entidades foram

Paradigmas de Linguagens de Programação - versão 2011 39

Prof. Kesede R Julio

herdadas, chamamos de classe-pai (superclasse). As chamadas aos

métodos de uma classe chamamos de mensagens. Cada mensagem

deve conter: o nome do objeto a qual pertence o método e o nome do

método que deseja executar. Assim, um programa orientado à objetos

é uma coleção de mensagens enviadas de um objeto à outro.

Podemos também modificar os métodos das classes herdadas.

Chamamos isto de override (sobreposto). O novo método

normalmente tem a mesma assinatura do método modificado. Usamos

este tipo de recurso quando queremos especificar uma operação,

mantendo a operação genérica na classe pai e especificando-a nas

classes-filha.

Dois tipos de herança são possíveis: simples e múltipla. A

herança simples ocorre quando temos apenas uma classe-pai. Já a

herança múltipla ocorre quando a classe herda de mais de uma

classe-pai. Podemos representar herança simples através de uma

árvore de derivação e a herança múltipla através de um grafo de

derivação.

Uma grande desvantagem da herança é a dependência gerada

entre as classes, algo que não acontece com Tipos Abstratos de Dados.

A forma geral de herança de classe em C++ é:

class classe_derivada : modo_de_acesso nome_da_classe

{declarações do membro de dados e função membro};

Os modos de proteção (private, protected e public) são

Paradigmas de Linguagens de Programação - versão 2011 40

B

DE

A M

F

Herança MúltiplaHerança simples

Prof. Kesede R Julio

redesenhados na derivação. Exemplo:

class classe_base{

private:

int a;

float x;

protected:

int b;

float y;

public:

int c;

float z;

};

class sub_classe_1 : public classe_base{ …};

class sub_classe_2 : private classe_base{ …};

Em sub_classe_1, “b”, “y” são protegidos , e “c”, e ”z” são

públicos. Nenhuma classe derivada de sub_classe_2 tem acesso a

qualquer membro de classe_base. Já os membros private de

classe_base “a” e “x”, não são acessíveis a qualquer membro de

sub_classes_1 e sub_classes_2.

Para que um membro de uma classe herdada como privada,

possa ser acessado pela derivação de sua classe-filha, devemos

reexportá-lo. Assim,

class sub_classe_3 : private classe_base{

classe_base :: c;

}

Agora, os objetos instanciados da sub_classe_3 podem

acessar o membro “c” da classe_base. Os dois pontos duplos (::) são

chamados de “operador de resolução de escopo” e especifica a qual

classe pertence a entidade definida.

3.3 Acoplamento dinâmico

É a ação do objeto, ao receber uma mensagem, de verificar

Paradigmas de Linguagens de Programação - versão 2011 41

Prof. Kesede R Julio

em sua classe se há um método que defina como ele deve se

comportar de acordo com a mensagem recebida. Se nada for

encontrado em sua classe ele irá verificar na superclasse da sua classe,

de onde ele possa ter herdado algo.

3.4 Polimorfismo

É, de certa forma, decorrente do acoplamento dinâmico, pois

é a característica em que, mesmo recebendo mensagens iguais, objetos

receptores de mensagem diferentes podem gerar respostas diferentes,

dependendo do método de sua classe. Mais importante, o objeto

emissor não precisa conhecer a classe do objeto receptor e como este

objeto irá responder à mensagem. Isto significa que, por exemplo, a

mensagem print pode ser enviada para um objeto sem a necessidade

de saber se o objeto é um caracter, um inteiro ou uma string. O objeto

receptor responde com o método que é apropriado à classe a qual ele

pertence.

3.5 Exemplo de linguagem: Python

Muitas linguagens de programação foram criadas desde o

início da computação, hoje em dia várias destas linguagens estão

“extintas” e podemos considerar que linguagens como C++, Java e

.NET são predominantes no mercado. O nome da linguagem é uma

homenagem ao grupo humorístico inglês Monty Python.

Python vem sendo usada em projetos sérios por entidades

como Yahoo, NASA, InfoSeek, MCI Worldcom, IBM e Hiway, a maior

empresa de hospedagem de web-sites do mundo. É também a base do

Zope, a mais sofisticada plataforma para construção de

webapplications disponível hoje como open-source.

Paradigmas de Linguagens de Programação - versão 2011 42

Prof. Kesede R Julio

3.5.1 Características

a. Programação orientada a objetos (incluindo herança múltipla, conceito apenas parcialmente presente em Java) ;

b. Exceções, um moderno mecanismo para o tratamento de erros;

c. Módulos, uma forma inteligente de acessar e organizar código a ser reutilizado ;

d. Coleta de lixo automática, sistema que elimina os erros causados pelo acúmulo de dados inúteis na memória do computador (característica presente também em Java, mas não em C++);

e. Recursos avançados de manipulação de textos, listas e outras estruturas de dados;

f. Portabilidade possibilidade de executar o mesmo programa sem modificações em várias plataformas de hardware e sistemas operacionais (uma virtude de Java, mas difícil de se conseguir em C++)

Portanto podemos perceber que o Python é uma linguagem

simples e robusta nos oferecendo a maior parte das características

importantes de linguagens modernas e amplamente utilizadas como

Java, C++, Perl e VBScript.

3.5.2 Elementos da linguagem

Tipos de variáveis:- Python é dinamicamente tipado, o que

significa que se você usou uma variável para armazenar um inteiro,

nada lhe impede de usá-la posteriormente para armazenar uma string

(frase). Na verdade, variáveis em Python não são declaradas, o que lhe

dá muita flexibilidade. Quem determina o tipo de uma variável é o

próprio interpretador, você dificilmente precisa se preocupar com o tipo

Paradigmas de Linguagens de Programação - versão 2011 43

Prof. Kesede R Julio

da variável, segue o exemplo abaixo:

• x = 2 # Inteiro• p = 3.1415 # Real, ou ponto flutuante• verdadeiro = True # Boolean• estringue = 'alguma frase' # String• c = 3 + 2j # Complexo• lista = [3, 4, 5] # Lista com elementos inteiros• lista2 = [2,'tres',4.0,[5,6]] # Lista mista e aninhada• tupla = (1,2,3,'quatro')

Tupla. É como uma lista, mas não pode ser mudada. tuplas de 0 ou 1 elementos têm sintaxes especiais: tupla0 = ( )tupla1 = ('primeiro e único item',)

Repare na vírgula. Em alguns casos (atribuições, returns), os

parênteses são opcionais, mas na maioria das vezes (tuplas dentro de

listas, tuplas dentro de chamadas de funções) os parênteses são

necessários porque a vírgula faz parte de outra sintaxe.

• coord = (4.5, 9.1)• cor_branca = 255, 255, 255 # cor no formato RGB• função ((4.5, 9.1), 'string')• dic = {'site':'Python Brasil','url':'www.pythonbrasil.com.br'}

#Isso é um dicionário

Abaixo listamos alguns os tipos primitivos implementados

na linguagem:

• str (Strings, como 'blabla')• unicode (São strs em que os caracteres podem representar

qualquer língua, u'blabla')• bool (True ou False)• float (Números em ponto flutuante, como 4.5 ou 3.14e-10)• int (Números inteiros, como 99)• long (Números inteiros muito grandes, como

1234567890123456789123456789L)• complex (Números complexos, como 3.1j ou 7+3.14e-10j)• file (Arquivos)• decimal (integrado ao Python 2.4 como módulo) (Números

fracionários representados deforma decimal em vez de binária)

Paradigmas de Linguagens de Programação - versão 2011 44

Prof. Kesede R Julio

Os tipos compostos implementados na linguagem, são: list,

tuple, dict, set.

Estruturas de Controle:- Algo interessante (e útil) em

Python é a forma de estruturação do script da linguagem. Não existe

nenhum caractere para delimitar blocos de códigos, como em outras

linguagens ({} em C/C++, begin/end em Pascal etc). A delimitação dos

blocos é dada pela endentação, ou seja, o programador é obrigado a

organizar o programa, pois programas desorganizados não rodarão

corretamente. Temos 3 estruturas de controle em Python, uma de

decisão (if-elif-else) e duas iterativas (for e while).

if-elif-else: O if o elif e o else servem para examinar

expressões. Em português eles avaliam se determinada expressão

retorna Verdadeiro ou Falso. Em Python os valores vazios: None, [] (*

Lista vazia), "", 0. São tidos como Falso. E os outros valores são

verdadeiros. Sintaxe:

if condição:

# bloco de código

elif condição:

# outro bloco

else:

# bloco final

Exemplo:

a = 2

b = 12

if a < 5 and b * a > 0:

print "ok"

Paradigmas de Linguagens de Programação - versão 2011 45

Prof. Kesede R Julio

Outro exemplo:

if nome == "pedro":

idade = 21

elif nome == "josé":

idade = 83

else:

idade = 0

for: Algo bastante diferente e útil em Python é a sua iteração

sobre listas. Veremos isto nos exemplos. A sintaxe geral do for é:

for variável in seqüência:

# bloco de código

else:

# bloco executado na ausência de um break

Exemplo 1: imprime números de 0 a 49

For i in range(50):

Print i

Exemplo 2: Mostra os nomes das estações contidos na lista

>>> estacoes = ["verao", "outono", "inverno",”primavera”]

>>> for e in estacoes:

... print e

...

verao

outono

inverno

primavera

Exemplo 3: a variável i, varia de 1 a 4 (exclusive)

Paradigmas de Linguagens de Programação - versão 2011 46

Prof. Kesede R Julio

>>> for i in range(1,4):

... print "%da volta" % i

...

1a volta

2a volta

3a volta

while: existe a iteração enquanto uma condição não é

satisfeita. A forma geral do while é:

while condição:

# bloco de código

else:

# bloco executado na ausência de um break

Exemplo 1:

opc = 1

while opc != 0:

# qualquer código

opc = int(raw_input("Digite 0 para sair \n > "))

Exemplo 2:

>>> m = 3 * 19

>>> n = 5 * 13

>>> contador = 0

>>> while m < n:

... m = n / 0.5

... n = m / 0.5

... contador = contador + 1

...

>>> print "Iteramos %d vezes." % contador

Paradigmas de Linguagens de Programação - versão 2011 47

Prof. Kesede R Julio

Iteramos 510 vezes.

Módulos:- Para criar um módulo em Python criamos um

arquivo com nome <arq>.py no diretório de seu programa principal.

Neste arquivo faça os defs e classes. No programa que chama o módulo

importe este modulo usando import <arq>. Desta forma podemos

reaproveitar o código com maior facilidade sem a necessidade de copiar

e colocar um determinado código para seu sistema.

Classes:- Definimos uma classe através da palavra “class”. O

método __init__() é o construtor da classe. Para referenciar atributos e

métodos dentro de uma classe, usamos o prefixo self. Exemplo:

class Carro:

def __init__(self, preco):

self.marca = "Ford"

self.modelo = "Maverick"

self.ano = 74

self.cor = [0,200,50]

self.pos = [0,0]

def andar(self, x, y):

self.pos[0] = self.pos[0]+x

self.pos[1] = self.pos[1]+y

carango = Carro(5000) #passa o valor 5000 para __init__()

for i in range(300):

#executa o for 300 vezes simulando que o carro andou

300 unidades de medida.

carango.andar(1,0)

Herança:- Permite que se utilize código de outras classes. O

Python permite herança simples e múltipla (pouco recomendada pela

complexidade do código). Exemplo:

# classe pai

class CD:

Paradigmas de Linguagens de Programação - versão 2011 48

Prof. Kesede R Julio

def __init__(self, titulo):

self.titulo = titulo

def mudarTitulo(self, novoTitulo):

self.titulo = novoTitulo

# sub-classe

class CDAudio(CD):

def __init__(self, titulo, autor):

CD.__init__(self, titulo)

self.autor = autor

self.faixas = []

def adicionarFaixa(self, numero, nome):

self.faixas.append((numero, nome))

def removerFaixa(self, numero, titulo):

self.faixas.remove((numero, nome))

novoCD = CDAudio('Physical Graphitte', 'Led Zeppelin')

novoCD.adicionarFaixa(1,'Custard Pie')

novoCD.adicionarFaixa(2,'The Rover')

print dir(novoCD) #mostra os atributos do objeto novoCD

print "CD: %s, %s" % (novoCD.titulo, novoCD.autor)

print novoCD.faixas

List :- Permite processar listas de uma forma bem próxima a

matemática. A sintaxe é:

[<expressão> for <variável> in <lista> if <condição>]

Exemplo:

#mostra os múltiplos de 4 no range de zero a cem

[x for x in range(100) if x % 4 == 0]

[0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76,

80, 84, 88, 92, 96]

Paradigmas de Linguagens de Programação - versão 2011 49

Prof. Kesede R Julio

4 Paradigma Lógico

A forma de programação das linguagens lógicas nada tem a

ver com as funcionais ou imperativas. Usamos aqui proposições

juntamente com a lógica simbólica afim de inferirmos novas

proposições. Damos a isto o nome de Cálculo de Predicados, que é a

base da programação lógica.

4.1 Proposições

Proposição é uma declaração lógica da relação entre objetos,

que pode ou não ser verdadeira. Os objetos podem ser termos simples,

constantes ou variáveis.

As proposições mais simples (proposições atômicas) são

formadas por termo composto. O termo composto consiste em um

functor, que nomeia a relação, e uma lista de parâmetros. Exemplo:

homem(jake)

gosta(bob, bife)

Neste caso, homem e gosta são functores e jake, bob, bife

são parâmetros. A primeira relação, com um único parâmetro

chamamos de 1-tupla e a segunda, com 2 parâmetros chamamos de

2-tupla. Esta nomenclatura segue de acordo com a quantidade de

parâmetros. Todos os termos simples desta relação são constantes. O

Paradigmas de Linguagens de Programação - versão 2011 50

Prof. Kesede R Julio

que dará significado as relações não serão seus nomes, mas sim o

contexto do problema a ser resolvido.

Podemos ligar duas proposições através de conectores

lógicos, formando assim, proposições compostas. A tabela abaixo

mostra os conectores e seus significados.

Nome Símbolo Exemplo Significado Precedência

Negação ¬ ¬a não a Mais alta

Conjunção ∩ a b∩ a e b

Disjunção ∪ a b∪ a ou b

Equivalência ≡ a b≡ a é equivalente a b

Implicação ⊃ a b⊃ a implica b

⊂ a b⊂ b implica aMais baixa

Exemplo de proposições compostas:

a ¬ b c∩ ⊃

Obedecendo a precedência, a avaliação desta proposição seria

realizada como:

(a (∩ ¬ b)) ⊃ c

Podemos acrescentar variáveis a nossas proposições, desde

que acompanhadas de quantificadores existenciais (existe ( )) e∃

universais (qualquer (V)) e separadores (.). Exemplos:

1. VX.(mulher(X) humano(X))⊃

2. ∃X.(mãe(mary,X) homem(X))∩

O exemplo 1 diz que qualquer valor de X, sendo X mulher, X é

humano. Toda mulher é humano.

O exemplo 2 diz que existe um X, cujo Mary é mãe e X é

homem. Mary tem um filho.

Os quantificadores tem precedência sobre todos os

Paradigmas de Linguagens de Programação - versão 2011 51

Prof. Kesede R Julio

operadores e seu escopo limita-se as proposições atômicas, a menos

que as proposições sejam estendidas por parênteses, como nos

exemplos acima.

4.2 Forma Clausal

A forma clausal é a maneira mais simples de se expressar

proposições e evitar redundâncias. A forma sintática geral é:

B1 B∪ 2 ... ∪ B∪ n ⊂ A1 A∩ 2 ... A∩ ∩ m

Aqui, se todos os A's forem verdadeiros, pelo menos um B

será verdadeiro. As conjunções (and) devem ser colocadas do lado

direito, e as disjunções (ou), do lado esquerdo. Esta forma dispensa os

quantificadores existenciais ( ) ∃ e os universais (V) ficam implícitos no

uso de variáveis nas proposições atômicas.

Qualquer cálculo de predicado pode ser convertido para forma

clausal. O lado direito de uma proposição chamamos de “antecedente”

e o lado esquerdo de “conseqüente”. Exemplos de proposições na

forma clausal:

1. gosta(bob, truta) ⊂ gosta(bob, peixe) ∩ peixe(truta)

2. pai(louis, al) ∪ pai(louis, violet) ⊂ pai(al, bob) ∩ mãe(violet,

bob) ∩ avô(louis, bob)

No primeiro exemplo, se bob gosta de peixe e peixe é truta,

logo bob gosta de truta.

No segundo exemplo, se al é pai de bob e violet é mãe de bob

e louis é avô de bob, logo louis é pai de violet ou louis é pai de al.

Este método também foi explorado para Demonstração de

Paradigmas de Linguagens de Programação - versão 2011 52

Prof. Kesede R Julio

Teoremas.

4.3 Cálculo de Predicados e Demonstração de Teoremas

O cálculo de predicados permite a inferência de novas

proposições a partir de proposições dadas. A esta regra de inferência,

damos o nome de Resolução. A resolução foi idealizada para ser

aplicada à forma clausal e se comporta da seguinte maneira:

Dadas as proposições

P1 ⊂ P2

Q1 ⊂ Q2

Neste caso, P2 implica P1 e Q2 implica Q1 .

Considerando que P1 seja idêntico a Q2 , podemos substituir P1 e

Q2 por T. Assim:

T ⊂ P2

Q1 ⊂ T

Podemos inferir que P2 implica Q1 , pois P2 implica T e

T implica Q1 .

Consideremos outras duas proposições:

mais velho (joanne, jake) ⊂ mãe(joanne, jake)

mais sábio(joanne, jake) ⊂ mais velho(joanne, jake)

Utilizando a mesma lógica de construção de resolução do

exemplo anterior, teríamos a nova proposição da seguinte forma:

mais sábio(joanne, jake) ⊂ mãe(joanne, jake)

Esta mecânica de construção é simples. Agrupamos os termos

do lado esquerdo das proposições utilizando um E, e fazemos a mesma

coisa do lado direito. Após o agrupamento, cancelamos os termos

Paradigmas de Linguagens de Programação - versão 2011 53

Prof. Kesede R Julio

iguais dos dois lados. Pronto, temos agora a nova proposição. Observe

o exemplo:

pai(bob, jake) ∪ mãe(bob, jake) ⊂ pais(bob, jake)

avô(bob, fred) ⊂ pai(bob, jake) ∩ pai(jake, fred)

aplicando a resolução:

mãe(bob, jake) ∪ avô(bob, fred) ⊂ pais(bob, jake) ∩ pai(jake,

fred)

A resolução complica quando inserimos variáveis nas

proposições, pois a resolução exige que seus valores sejam

encontrados, a fim de que a comparação seja bem sucedida. O valor

encontrado nem sempre é satisfatório e um outro valor deverá ser

atribuído para nova avaliação. Ao processo de determinação de valores,

damos o nome de unificação e a atribuição temporária de valores às

variáveis que permite a unificação, damos o nome de instanciação.

Um uso fundamental da resolução é na demonstração de

teorema. Assim, construímos as nossas hipóteses através de

proposições pertinentes. Uma outra proposição é construída a fim de

negar as proposições anteriores, chamamos isto de meta. Este tipo de

demonstração é a prova pela contradição. O objetivo aqui é encontrar

uma contradição entre as proposições dadas.

Proposições usadas na resolução, obedecem uma forma

clausal restrita e pode ser escrita apenas de duas formas: cláusulas

com cabeça (relações) e sem cabeça (fatos) (Cláusulas de Horn).

Exemplo de relação:

gosta(bob, truta) ⊂ gosta(bob, peixe) ∩ peixe(truta)

exemplo de fato:

pai(bob, jake)

Paradigmas de Linguagens de Programação - versão 2011 54

Prof. Kesede R Julio

Nas Cláusulas de Horn existem apenas uma proposição

atômica do lado esquerdo, ou nenhuma. A grande maioria das

proposições podem ser declaradas desta forma.

4.4 Elementos básicos do Prolog

Termos:- Um termo pode ser uma constante, uma variável

ou uma estrutura.

Uma constante é um átomo ou um número inteiro. O átomo

pode ser um conjunto de letras, dígitos etc , e se inicia com letra

minúscula. Ex.: jake

Uma variável é um conjunto de letras, dítos etc, e se inicia

com letra maiúscula. Ex.: X

Uma estrutura é uma proposição, que pode ser um fato, uma

relação ou uma consulta. Ex.: homem(Vinicius)

Fatos :- (Cláusula de Horn, sem-cabeça) São proposições

dadas como verdadeiras e utilizadas para para consulta, a fim de inferir

um novo fato ou relação. São sempre incondicionais. Ex.:

mulher (shelley). (shelley é mulher)

homem(bill). (bill é homem)

pai(bill, jake). (bill é pai de jake)

No último exemplo, apenas pré-supomos a semântica, pois

não temos um contexto.

Regras :- (Cláusula de Horn, com-cabeça) são proposições

condicionais contendo um antecedente (lado direito) e um conseqüente

(lado esquerdo). Se (if) o lado direito for verdadeiro, o lado esquerdo

também será (then). O antecedente pode conter várias proposições

Paradigmas de Linguagens de Programação - versão 2011 55

Prof. Kesede R Julio

amarradas por conjunções, porém o lado conseqüente será atômico.

Uma conjunção em Prolog é denotado por “,” (vírgula). Ex.:

mulher(shelley), filho(shelley).

A forma geral de uma regra é:

conseqüente :- antecedente.

Os caracteres “:-” simbolizam o implica (⊂) da programação

lógica. Assim, conseqüente só será resolvido se antecedente for

verdadeiro, ou tornar-se verdadeiro através da instanciação de suas

variáveis. Ex.:

antepassado(mary, shelley) :- mãe(mary, shelley).

Neste caso, se mary for mãe de shelley, mary é antepassado

de shelley.

Podemos também usar variáveis para que o significado das

proposições sejam generalizadas. Exs.:

pais(X, Y) :- mãe(X, Y).

pais(X, Y) :- pai(X, Y).

avô(X, Z) :- pai(X, Y), pai(Y, Z).

irmãos(X, Y) :- mãe(M, X), mãe(M, Y), pai(F, X), pai(F, Y).

O significado do conjunto de cláusulas acima é:

se X é mãe de Y, então X é um dos pais de Y.

se X é pai de Y, então X é um dos pais de Y.

se X é pai de Y e Y é pai de Z, então X é avô de Z.

Se M é mãe de X e M é mãe de Y e F é pai de X e F é pai de Y,

então X e Y são irmãos.

Metas :- (Cláusula de Horn, sem-cabeça) As metas ou

consultas são proposições para verificação de aprovação ou

desaprovação de um teorema, a partir de uma base de conhecimento

Paradigmas de Linguagens de Programação - versão 2011 56

Prof. Kesede R Julio

constituído por fatos e regras. Ex.:

homem(fred).

Neste caso, o sistema responderá “yes” ou “no”. “Yes”,

significa que a meta é verdadeira considerando a base de

conhecimento. “No”, significa que que a meta é falsa ou que o sistema

é incapaz de prová-la.

Podemos fazer consultas usando variáveis, assim o sistema

instancia a variável para provar a veracidade da proposição. Ex.:

pai(X, mike).

Através da unificação o sistema tentará um valor para X que

resulte na veracidade da proposição, caso não encontre retornará “no”.

Processo de Inferência :- Para que o Prolog conclua uma

meta, é necessário encontrar na base de conhecimento um fato ou

então um encadeamento de fatos e regras que possam torná-la

verdadeira. Quando temos uma proposição composta como meta, cada

um dos fatos torna-se uma submeta. Assim, para satisfazer a meta Q,

poderíamos ter:

P2 :- P1

P3 :- P2

Q :- Pn

Considere agora a consulta abaixo:

homem(bob).

Caso a base de conhecimento contenha o fato homem(bob),

temos a conclusão da meta satisfeita de forma direta, porém considere

a base de conhecimento abaixo.

pai(bob).

Paradigmas de Linguagens de Programação - versão 2011 57

Prof. Kesede R Julio

homem(X) :- pai(X).

A conclusão não é trivial uma vez que não temos um fato para

casarmos diretamente com a meta. Assim, o sistema procurará o

primeiro termo contendo o functor da meta e fará a instanciação da

variável X para bob. Portanto homem(X) tornará homem(bob), e com

esta instanciação feita, pai(X) torna-se pai(bob). pai(bob) torna-se

verdadeira, a partir do primeiro fato da base. A este tipo de inferência

usada pelo Prolog chamamos de “encadeamento retrógrado”.

Um outro recurso usado pelo Prolog para inferir metas, é

chamado de backtracking. Ele é usado quando uma meta de proposição

composta (ou seja, com submetas) tenta ser inferida. O sistema tenta

provar uma submeta, e na falha desta tentativa, retorna tentando

provar a submeta anterior. Isto pode se tornar bem complexo e

demorado, dependendo da organização da base de conhecimento e da

meta. Considere o exemplo de meta abaixo: Existe um homem X, tal

que X seja um dos pais de shelley?

homem(X), pais(X, shelley).

Neste caso, o sistema procurará um fato com o functor igual

ao da primeira meta. Encontrando, ele verificará se esta instancia de X

é um dos pais de shelley. Caso não seja, o sistema retorna

(backtracking) para o próximo functor homem para uma nova

instanciação e tentará novamente provar que esta nova instanciação é

um dos pais de shelley. Seria muito mais rápida a busca se as

proposições da meta estivessem invertidas (pais(X, shelley),

homem(X)). Assim, o sistema primeiro procuraria um X que fosse um

dos pais de shelley e depois verificaria se X é homem, ao invés de

procurar um homem e depois verificar se o mesmo é um dos pais de

Paradigmas de Linguagens de Programação - versão 2011 58

Prof. Kesede R Julio

shelley. Afirmamos isto porque, supostamente existiriam na base bem

mais homens, do que pais de shelley.

Passamos agora a descrever este processo de resolução do

Prolog através de um exemplo de computação numérica.

O operador “is” do Prolog permite a instanciação de uma

variável por uma expressão. Exemplo:

A is B / 17 + C.

Se A não estiver instanciada, mas B e C estiverem, A receberá

o valor da expressão. Assim, a cláusula é satisfeita. Porém, se A estiver

instanciado e B ou C não estiverem, a cláusula não será satisfeita.

Cuidado o “is” não funciona como um comando de atribuição das

linguagens imperativas.

Consideremos agora uma base de conhecimento contendo a

velocidade média de vários carros, assim como o tempo que cada um

deles permaneceram na pista. Estes serão nossos fatos. A distância que

cada um percorreu pode ser dada como uma relação. Assim, teremos a

seguinte base de conhecimento:

velocidade(ford, 100).

velocidade(chevy, 105).

velocidade(dodge, 100).

velocidade(volvo, 100).

tempo(ford, 20).

tempo(chevy, 21).

tempo(dodge, 24).

tempo(volvo, 24).

distância(X, Y) :- velocidade(X, Velocidade), tempo(X,

Tempo), Y is Velocidade * tempo.

Consideremos agora uma consulta que deseja descobrir qual a

Paradigmas de Linguagens de Programação - versão 2011 59

Prof. Kesede R Julio

distância percorrida pelo chevy:

distância(chevy, Distância_do_Chevy).

Para entendermos a execução desta consulta, vamos recorrer

à uma estrutura do Prolog chamada “trace”. Esta estrutura permite o

rastreamento da execução passo-a-passo. Vamos entender primeiro os

4 eventos possíveis do “trace”. (1) Call :- tentativa de satisfazer uma

meta. (2) Exit:- meta satisfeita. (3) Redo :- tentativa de satisfazer

novamente a meta. (4) fail :- meta não satisfeita. Call e Exit podem ser

entendidos como uma chamada e retorno de função, respectivamente.

Voltemos ao exemplo usando o trace.

trace.

distância(chevy, Distância_do_Chevy).

(1) 1 Call: distância(chevy, _0)?

(2) 2 Call: velocidade(chevy, _5)?

(2) 2 Exit: velocidade(chevy, 105)

(3) 2 Call: tempo(chevy, _6)?

(3) 2 Exit: tempo(chevy, 21)

(4) 2 Call: _0 is 105*21?

(4) 2 Exit: 2205 is 105*21

(1) 1 Exit: distância(chevy, 2205)

Distância_do_Chevy = 2205

O caractere “_” (underline) é usado para identificar variáveis

a serem instanciadas pelo sistema. Existem 4 colunas mostradas pelo

trace. A 1ª mostra um número que corresponde a submeta a ser

Paradigmas de Linguagens de Programação - versão 2011 60

Prof. Kesede R Julio

satisfeita. A 2ª mostra a profundidade da chamada. A 3ª mostra o tipo

de evento. A 4ª mostra a própria linha de instrução Prolog.

Neste exemplo, nenhum evento “redo” é executado, pois o

backtracking é desnecessário. Vejamos agora um exemplo usando

backtracking.

Consideremos a base de conhecimento abaixo:

gosta(jake, chocolate).

gosta(jake, damascos).

gosta(darcie, alcaçuz).

gosta(darcie, damascos).

Agora consideremos a consulta rastreada.

trace.

gosta(jake, X), gosta(darcie, X).

(1) 1 Call: gosta(jake, _0)?

(1) 1 Exit: gosta(jake, chocolate)

(2) 1 Call: gosta(darcie, chocolate)?

(2) 1 Fail: gosta(darcie, chocolate)

(2) 1 Redo: gosta(jake, _0)?

(1) 1 Exit: gosta(jake, damascos)

(3) 1 Call: gosta(darcie, damascos)?

(3) 1 Exit: gosta(darcie, damascos)

X = damascos

Agora veremos uma segunda estrutura básica do Prolog: a

estrutura de Lista. Exemplo:

Paradigmas de Linguagens de Programação - versão 2011 61

Prof. Kesede R Julio

[maça, ameixa seca, uvas, kumquat]

Uma lista sempre apareça entre colchetes e [] denotará uma

lista vazia.

Não há uma função especifica para construir ou dividir uma

lista. Para isto usamos a seguinte notação: [X | Y], onde o elemento

antes do “|” (pipe) é chamado de cabeça da lista e o segundo de cauda.

Abaixo mostramos proposições usando lista.

nova_lista([maça, ameixa seca, uvas, kumquat]).

nova_lista([damasco, pêssego, pera]).

Poderíamos agora formular uma consulta capaz de dividir a

lista. Assim:

nova_lista([Cabeça_da_Nova_Lista| Cauda_da_Nova_Lista])

Neste caso, Cabeça_da_Nova_Lista seria instanciado com o

elemento maça e Cauda_da_Nova_Lista seria instanciado com [ameixa

seca, uvas, kumquat].

Podemos também criar listas usando a mesma notação.

[Elemento_1 | Lista_2]

Se elemento_1 for instanciado com picles e Lista_2 com

[amendoim, ameixa seca, pipoca], teremos uma nova lista [picles,

amendoim, ameixa seca, pipoca].

O Prolog também nos fornece um operador para concatenar

Paradigmas de Linguagens de Programação - versão 2011 62

Prof. Kesede R Julio

listas chamada “append”. Por exemplo:

append([], Lista, Lista).

append([Cabeça | Lista_1], Lista_2, [Cabeça | Lista_3]) :-

append(Lista_1, Lista_2, Lista_3).

Na 1ª proposição afirmamos que uma lista vazia anexada à

Lista resultará na própria Lista.

Na 2ª proposição afirmamos que a Cabeça da Lista_1, quando

Lista_1 for anexada à Lista_2 será Cabeça também na Lista_3

resultante, apenas quando Lista_1 anexada à Lista_2 resultar na

Lista_3.

Vejamos agora um exemplo de resolução de listas em Prolog,

executando uma consulta à base de conhecimento acima com o auxílio

do trace.

trace.

append([bob, jo], [jake, darcie], Família) .

(1) 1 Call: append([bob, jo], [jake, darcie], _10) ?

(2) 2 Call: append([jo],[jake, darcie], _18)?

(3) 3 Call: append([],[jake, darcie], _25)?

(3) 3 Exit: append([],[jake, darcie], [jake, darcie])

(2) 2 Exit: append([jo],[jake, darcie], [jo, jake, darcie])

(1) 1 Exit: append([bob, jo],[jake, darcie], [bob, jo, jake,

darcie])

Familia = [bob, jo, jake, darcie]

yes

Paradigmas de Linguagens de Programação - versão 2011 63

Prof. Kesede R Julio

4.5 Exemplos de Linguagem: Prolog

Exemplo 1 :- Um programa que calcula o fatorial de um

número dado.

Base de conhecimento (fatos e regras): Arquivo .pl

fatorial(0,1).

fatorial(N, F) :-

N>0,

N1 is N-1,

fatorial(N1, F1),

F is N * F1,

!.

Meta rastreada: a ser digitado no prompt do Prolog

?- trace.

?- fatorial(5, X).

Resultado: Mostrado pelo sistema do Prolog

Call: (7) fatorial(5, _G930) ? creep

^ Call: (8) 5>0 ? creep

^ Exit: (8) 5>0 ? creep

^ Call: (8) _L174 is 5-1 ? creep

^ Exit: (8) 4 is 5-1 ? creep

Call: (8) fatorial(4, _L175) ? creep

^ Call: (9) 4>0 ? creep

^ Exit: (9) 4>0 ? creep

^ Call: (9) _L193 is 4-1 ? creep

^ Exit: (9) 3 is 4-1 ? creep

Call: (9) fatorial(3, _L194) ? creep

Paradigmas de Linguagens de Programação - versão 2011 64

Prof. Kesede R Julio

^ Call: (10) 3>0 ? creep

^ Exit: (10) 3>0 ? creep

^ Call: (10) _L212 is 3-1 ? creep

^ Exit: (10) 2 is 3-1 ? creep

Call: (10) fatorial(2, _L213) ? creep

^ Call: (11) 2>0 ? creep

^ Exit: (11) 2>0 ? creep

^ Call: (11) _L231 is 2-1 ? creep

^ Exit: (11) 1 is 2-1 ? creep

Call: (11) fatorial(1, _L232) ? creep

^ Call: (12) 1>0 ? creep

^ Exit: (12) 1>0 ? creep

^ Call: (12) _L250 is 1-1 ? creep

^ Exit: (12) 0 is 1-1 ? creep

Call: (12) fatorial(0, _L251) ? creep

Exit: (12) fatorial(0, 1) ? creep

^ Call: (12) _L232 is 1*1 ? creep

^ Exit: (12) 1 is 1*1 ? creep

Exit: (11) fatorial(1, 1) ? creep

^ Call: (11) _L213 is 2*1 ? creep

^ Exit: (11) 2 is 2*1 ? creep

Exit: (10) fatorial(2, 2) ? creep

^ Call: (10) _L194 is 3*2 ? creep

^ Exit: (10) 6 is 3*2 ? creep

Exit: (9) fatorial(3, 6) ? creep

^ Call: (9) _L175 is 4*6 ? creep

^ Exit: (9) 24 is 4*6 ? creep

Exit: (8) fatorial(4, 24) ? creep

^ Call: (8) _G930 is 5*24 ? creep

^ Exit: (8) 120 is 5*24 ? creep

Paradigmas de Linguagens de Programação - versão 2011 65

Prof. Kesede R Julio

Exit: (7) fatorial(5, 120) ? creep

X = 120 ;

Redo: (12) fatorial(0, _L251) ? creep

^ Call: (13) 0>0 ? creep

^ Fail: (13) 0>0 ? creep

Fail: (11) fatorial(1, _L232) ? creep

Fail: (10) fatorial(2, _L213) ? creep

Fail: (9) fatorial(3, _L194) ? creep

Fail: (8) fatorial(4, _L175) ? creep

Fail: (7) fatorial(5, _G930) ? creep

false.

Obs.: Este resultado não considera o operador “!” (cut) que

serve para cancelar o bracktracking, caso as proposições já tenham

sido validadas. A tentativa , neste caso, é validar a proposição

fatorial(N1, F1), com N1 valendo “0” (zero). Como a primeira

proposição condicional já é “Fail” e não recursiva, o sistema tenta

validar a proposição fatorial(0,1) para todos os valores de N (de 1 a 5)

e evidentemente retornará “Fail”, pois deveria ser “0” (zero), o

primeiro parâmetro.

O diagrama de execução do cálculo do fatorial considerando o

operador “cut” seria:

Paradigmas de Linguagens de Programação - versão 2011 66

Prof. Kesede R Julio

Paradigmas de Linguagens de Programação - versão 2011 67

N = 5 > 0 → OK F = ? 120N1 = 4 is 5 - 1 → OKfatorial(4, F1 = ? 24) :- ...

N = 4 > 0 → OK F = ? 24N1 = 3 is 4 - 1 → OKfatorial(3, F1 = ? 6) :- ...

N = 3 > 0 → OK F = ? 6N1 = 2 is 3 - 1 → OKfatorial(2, F1 = ? 2) :- ...

N = 2 > 0 → OK F = ? 2N1 = 1 is 2 - 1 → OKfatorial(1, F1 = ? 1) :- ...

N = 1 > 0 → OK F = ? 1N1 = 0 is 1 - 1 → OKfatorial(0, F1 = ? 1) :- ...

fatorial(0, 1). → OK

F = 1 is 1 * 1

F = 2 is 2 * 1

F = 6 is 3 * 2

F = 24 is 4* 6

F = 120 is 5 * 24

fatorial(5, X 120) :- ...

X = 120

Prof. Kesede R Julio

Exemplo 2: Um programa que recebe o nome do usuário e

diz um Olá personalizado.

Base de conhecimento (fatos e regras): Arquivo .pl

ola :- read(X), write('Olá '), write(X). ]

Meta rastreada: a ser digitado no prompt do Prolog

?- trace.

?- ola.

Resultado: Mostrado pelo sistema do Prolog

Call: (7) ola ? creep

Call: (8) read(_L172) ? creep

|: kesede. (digitado pelo usuário)

Exit: (8) read(kesede) ? creep

Call: (8) write('Olá ') ? creep

Olá

Exit: (8) write('Olá ') ? creep

Call: (8) write(kesede) ? creep

kesede

Exit: (8) write(kesede) ? creep

Exit: (7) ola ? creep

true.

O comando read permite a instanciação direta da variável

enviada por parâmetro. O programa é simples e não tem backtraking.

Paradigmas de Linguagens de Programação - versão 2011 68

Prof. Kesede R Julio

Exemplo 3: Apagar um elemento de uma lista.

Base de conhecimento (fatos e regras): Arquivo .pl

del(X,[X|R],R).

del(X,[H|R1],[H|R2]):-del(X,R1,R2).

Meta rastreada: a ser digitado no prompt do Prolog

trace.

del(3,[1,2,3],X).

Resultado: Mostrado pelo sistema do Prolog

Call: (7) del(3, [1, 2, 3], _G983) ? creep

Call: (8) del(3, [2, 3], _G1058) ? creep

Call: (9) del(3, [3], _G1061) ? creep

Exit: (9) del(3, [3], []) ? creep

Exit: (8) del(3, [2, 3], [2]) ? creep

Exit: (7) del(3, [1, 2, 3], [1, 2]) ? creep

X = [1, 2] ;

Redo: (9) del(3, [3], _G1061) ? creep

Call: (10) del(3, [], _G1064) ? creep

Fail: (10) del(3, [], _G1064) ? creep

Fail: (8) del(3, [2, 3], _G1058) ? creep

Fail: (7) del(3, [1, 2, 3], _G983) ? creep

false.

Paradigmas de Linguagens de Programação - versão 2011 69

Prof. Kesede R Julio

5 Paradigma Funcional

Nos anos 30 foram desenvolvidos dois modelos abstratos para

computação: o modelo de Alan Turing e o modelo de Alonzo Church.

O modelo de Turing teve como base a máquina de Turing,

baseada em memória, estado e transições, pilar para construção da

arquitetura dos computadores de John Von Newman. Dessa idéia

surgiram as linguagens imperativas, que por sua vez deram origem as

linguagens orientadas à objeto.

Já o modelo de Alonzo Church baseou-se na teoria

Cálculo-lambda. Este modelo baseado em funções, deu origem às

linguagens funcionais, cujo objetivo é encontrar a imagem

correspondente ao domínio recebido como argumento de uma

determinada função, sendo que esta imagem poderá ou não ser

domínio de uma outra função. A estas transformações damos o nome

de “reduções”. Algumas linguagens incorporam conceitos

“extra-funcionais”, ou seja, alguns recursos típicos de linguagens

imperativas (entrada e saída de dados, variáveis), facilitando a

construção de programas, porém dificultando a análise dos programas

construídos.

5.1 Fundamentos

Um programa é, basicamente, composto por uma única

expressão que por sua vez é uma função. Uma função, assim como

seus parâmetros podem ser parâmetros ou valores de retorno de

outras funções. A solução de um problema ou redução do programa,

consiste em substituir parte da expressão principal por outras

expressões, obedecendo algumas regras de reescrita. Precisamos

Paradigmas de Linguagens de Programação - versão 2011 70

Prof. Kesede R Julio

ficar atentos a três propriedades principais para este conjunto de

regras: correção, confluência, terminação.

A correção, uma propriedade semântica, diz respeito a

preservação do significado das expressões, ou seja, a interpretação de

uma expressão deve ser a mesma, antes e depois da redução.

A confluência e a terminação são propriedades sintáticas e

garantem que sejam construídas regras eficientes para redução de

qualquer expressão de forma puramente funcional. A confluência diz

que se mais de uma regra for aplicável a uma mesma expressão, a

ordem de aplicação deve ser indiferente. A terminação garante que

um conjunto de regras aplicadas à redução de uma expressão não

produza uma expressão idêntica a ela, pois neste caso, não teríamos

solução do problema.

Através do λ-cálculo, podemos construir regras que

satisfazem as três propriedades acima. O lambda-cálculo tem 3

operações básicas: substituição, aplicação e abstração.

A substituição permite a troca de uma variável em uma

expressão por uma outra expressão. Assim, temos que:

F[X <- G] significa que X será trocado por G em todas as

ocorrências da expressão F.

Exemplo: Seja F uma expressão X+5

F[X <- 4] ≡4+5 ≡ 9

A aplicação permite a avaliação de uma expressão através

de um dado elemento de seu domínio. Então:

(λX.(X+5))4, significa que a variável X será trocada por 4, ou

seja, particulariza a expressão para o valor X=4. Neste caso, a

expressão será avaliada com este valor, ou seja, 4+5=9.

A abstração λX.(X+5) diz que existirá uma associação (X+5)

Paradigmas de Linguagens de Programação - versão 2011 71

Prof. Kesede R Julio

para cada valor de X. Neste caso, X não será um valor arbitrário, a

expressão se aplicará a todos os valores de X.

5.2 Funções Simples

Uma função geralmente é escrita através da definição de seu

nome, uma lista de argumentos entre parênteses e, logo depois, a

expressão correspondente. Exemplo:

cubo(x)≡x*x*x

tal que x seja um número real

Neste caso, x pode representar qualquer elemento do

domínio, no entanto, para que a função seja avaliada, x terá um valor

especificado. O símbolo “≡” é lido como “é definido como”

Através da notação lambda-cálculo podemos definir funções

sem especificação de nome. Ex.:

(λ(x)x*x*x)(2)

O resultado é 8.

5.3 Forma Funcional

Podemos dizer que uma função é de forma funcional quando

tem como parâmetro uma função e/ou produz uma função como

resultado. Uma das maneiras de descrever uma função de forma

funcional é através da composição de funções.

Na composição de funções, a função tem dois parâmetros

funcionais e resulta em um valor da primeira função aplicada à

segunda. Representamos este tipo de função através do operador o.

Assim como:

h≡f o g

Por exemplo, se:

f(x)≡x+2

Paradigmas de Linguagens de Programação - versão 2011 72

Prof. Kesede R Julio

g(x)≡3*x

h(x)≡f(g(x)) ou h(x)≡(3*x)+2

Outra maneira de escrevermos em forma funcional é através

da construção, que recebe uma lista de funções como parâmetro.

Quando aplicada a um argumento, ela aplica todos os parâmetros

funcionais a este argumento. As funções devem estar entre colchetes

“[]”. Dado que:

g(x)≡x*x

h(x)≡2*x

i(x)≡x/2

Portanto,

[g,h,i](4) resulta em (16,8,2)

O “Aply-to-all” (aplica-se a tudo) é uma forma funcional que

permite uma única função como parâmetro. Quando aplicada a uma

lista de argumentos, ele aplica seu parâmetro funcional a cada

argumento da lista e retorna o resultado em outra lista. Dado que:

h(x)≡x*x

então,

(h, (2,3,4)) resulta em (4,9,16)

5.4 A Linguagem Scheme

O Scheme é um dialeto do Lisp. É pequeno e tem semântica

simples. Tem com principal aplicação a educação (ensino do paradigma

funcional).

5.4.1 Funções primitivas

Nas expressões que chamam funções primitivas, todos os

Paradigmas de Linguagens de Programação - versão 2011 73

Prof. Kesede R Julio

parâmetros são avaliados e depois disso, a função primtiva é aplicada

aos seus valores e o resultado é mostrado.

Os operadores matemáticos, assim chamados nas linguagens

imperativas, são exemplos de funções primitivas em Scheme: +, -,

*, /. Cada um deles exerce a operação correspondente sobre seus

parâmetros. A tabela abaixo mostra alguns exemplos.

Expressão Retorno

42 42

(* 3 7) 21

(+ 5 7 8) 20

(- 5 6) -1

(- 15 7 2) 6

(- 24 (* 4 3)) 12

Já dissemos que os parâmetros são sempre avaliados, pois os

mesmos podem ser chamadas de funções, porém nem sempre isto

acontece. Os parâmetros podem ser listas, átomos ou elementos de

dados, e nestes casos, não devem ser avaliados. Para que isto aconteça

este tipo de parâmetro primeiramente são passados como parâmetro

para uma função primitiva QUOTE (´). Assim:

Expressão Retorno

(' A) A

(' (A B C)) (A B C)

Como Scheme tem como principal função a manipulação de

listas, a linguagem inclui também algumas funções primitivas para

divisão e construção de listas: CAR, CDR e CONS.

CAR :- Retorna a cabeça da lista.

Paradigmas de Linguagens de Programação - versão 2011 74

Prof. Kesede R Julio

Expressão Retorno

(CAR '(A B C)) A

(CAR '((A B) C D)) (A B)

(CAR 'A) Erro, pois A não é lista

(CAR '(A)) A

(CAR '()) Erro, pois lista vazia não tem cabeça

CDR :- Retorna a cauda da lista

Expressão Retorno

(CDR '(A B C)) (B C)

(CDR '((A B) C D)) (C D)

(CDR 'A) Erro, pois A não é lista

(CDR '(A)) ()

CONS :- constrói uma lista a partir dos dois argumentos

passados. A cabeça é o primeiro parâmetro e o segundo sua cauda.

Expressão Retorno

(CONS 'A '()) (A)

(CONS 'A '(B C)) (A (B C))

(CONS '() '(A B)) (() (A B))

(CONS '(A B) '(C D)) ((A B) (C D))

Podemos também construir listas a partir de um número de

parâmetros maior que 2, suando LIST. Exemplo:

(LIST 'maça 'laranja 'uva)

equivale a

(CONS 'maça (CONS 'laranja (CONS 'uva '() )))

5.4.2 Funções que constroem funções

Podemos usar a função DEFINE para criarmos uma função em

Scheme. Sua forma mais simples é:

Paradigmas de Linguagens de Programação - versão 2011 75

Prof. Kesede R Julio

(DEFINE simbolo expressão)

Como nos exemplos abaixo:

(DEFINE pi 3.14159)

(DEFINE dois_pi (* 2 pi))

Nestes casos, pi assume o valor 3.14159 e dois_pi 6,28318,

respectivamente.

Outra forma de usarmos DEFINE é através de duas listas

como parâmetro. Sua forma é:

(DEFINE (nome_da_função parâmetros) (corpo))

Os parâmetros são separdos por espaços e o corpo é uma

seqüência de expressão em forma de lista. Exemplo:

(DEFINE (quadrado numero) (* numero numero))

Ao usarmos esta expressão na forma:

(quadrado 5), teríamos como retorno 25.

Outro exemplo, agora para o cálculo da hipotenusa, dado os

outros dois lados.

(DEFINE (hipotenusa lado1 lado2)

(SQRT (+ (quadrado lado1) (quadrado lado2)))

)

5.4.3 Funções de Predicados

Função de predicado retorna um valor booleano. O Scheme

retorna #T para verdadeiro e () para falso, ou seja, qualquer lista não

vazia é considerada verdadeiro. Existem 3 funções pré-definidas: EQ?,

que compara a igualdade de dois átomos simbólicos; NULL?, que

verifica se uma lista é vazia; e LIST?, que verifica se seu argumento é

Paradigmas de Linguagens de Programação - versão 2011 76

Prof. Kesede R Julio

uma lista. Exemplos:

Expressão Retorno

(EQ? 'A 'A) #T

(EQ? 'A 'B) ()

(EQ? 'A '(A B)) ()

(LIST? '(X Y)) #T

(LIST? 'X) ()

(LIST? '()) #T

(NULL? '(A B)) ()

(NULL? '()) #T

(NULL 'A) ()

(NULL? '( () ) ) ()

Além destas funções de predicados, existem algumas funções

para dados numéricos.

Função descrição

= igual

<> diferente

> maior

>= Maior ou igual

< menor

<= Menor ou igual

EVEN? Verifica se é par

ODD? Verifica se é ímpar

ZERO? Verifica se é zero

EQV? Verifica se dois átomos (simbólicos ou numéricos) são

iguais

Outras duas funções são: DISPLAY e NEWLINE.

Paradigmas de Linguagens de Programação - versão 2011 77

Prof. Kesede R Julio

5.4.4 Fluxo de Controle

Em Scheme, os loopings são feitos de forma recursiva. Para

isto, duas estruturas de controle estão disponíveis na linguagem. IF,

para controle bidirecional e COND, seleções múltiplas.

Forma geral do IF:

(IF predicado expressão_then expressão_else)

Exemplo:

(DEFINE (fatorial n)

(IF (= n 0)

1

(* n (fatorial (- n 1) ) )

)

)

Forma geral do COND:

(COND

(predicado_1 expressão {expressão})

(predicado_2 expressão {expressão})

...

(predicado_n expressão {expressão})

(ELSE expressão {expressão})

)

Exemplo:

(DEFINE (Compare x y)

(COND

((> x y) (DISPLAY “x é maior que y”))

Paradigmas de Linguagens de Programação - versão 2011 78

Prof. Kesede R Julio

((< x y) (DISPLAY “x é menor que y”))

(ELSE (DISPLAY “x e y são iguais”))

)

)

5.4.5 Exemplos

Exemplo 1 :- A função chama-se “membro” e verifica se um

átomo é ou não membro de uma lista.

(DEFINE (membro atm lis)

(COND

((NULL? lis) '() )

((EQ? atm (CAR lis)) (membro atm (CDR lis))

)

)

)

Exemplo 2 :- A função chama-se “igualsimples” e compara

duas listas dadas como parâmetro para verificação de igualdade.

(DEFINE (igualsimples lis1 lis2)

(COND

((NULL? lis1) (NULL? lis2)) #T se lis1 e lis2 vazia

Paradigmas de Linguagens de Programação - versão 2011 79

Prof. Kesede R Julio

((NULL? lis2) '()) #T se lis2 vazia

((NULL? lis1) '()) #T se lis2 vazia

#T se CAR iguais e chama recursiva, senão retorna ()

((EQ? (CAR lis1) (CAR lis2))

(igualsimples (CDR lis1) (CDR lis2)))

(ELSE '())

)

)

Kesede Rodrigues Julio

[email protected]

Paradigmas de Linguagens de Programação - versão 2011 80