SlideShare uma empresa Scribd logo
1 de 80
Baixar para ler offline
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ântico
verifica se há conflitos difíceis/impossíveis de
serem resolvidos pelo Parser. Ex.: coersão de tipos
Gerador de código de máquina
constroe 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 de
forma 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 a
Mais 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 = ? 120
N1 = 4 is 5 - 1 → OK
fatorial(4, F1 = ? 24) :- ...
N = 4 > 0 → OK F = ? 24
N1 = 3 is 4 - 1 → OK
fatorial(3, F1 = ? 6) :- ...
N = 3 > 0 → OK F = ? 6
N1 = 2 is 3 - 1 → OK
fatorial(2, F1 = ? 2) :- ...
N = 2 > 0 → OK F = ? 2
N1 = 1 is 2 - 1 → OK
fatorial(1, F1 = ? 1) :- ...
N = 1 > 0 → OK F = ? 1
N1 = 0 is 1 - 1 → OK
fatorial(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
kesedejulio@gmail.com
Paradigmas de Linguagens de Programação - versão 2011 80

Mais conteúdo relacionado

Mais procurados

Paradigmas de Linguagens de Programação - Quatro Paradigmas + Ambientes de Pr...
Paradigmas de Linguagens de Programação - Quatro Paradigmas + Ambientes de Pr...Paradigmas de Linguagens de Programação - Quatro Paradigmas + Ambientes de Pr...
Paradigmas de Linguagens de Programação - Quatro Paradigmas + Ambientes de Pr...Adriano Teixeira de Souza
 
Conceitos Iniciais de Linguagens de Programação
Conceitos Iniciais de Linguagens de ProgramaçãoConceitos Iniciais de Linguagens de Programação
Conceitos Iniciais de Linguagens de ProgramaçãoSidney Roberto
 
Paradigmas de Linguagens de Programação - Classificações
Paradigmas de Linguagens de Programação - ClassificaçõesParadigmas de Linguagens de Programação - Classificações
Paradigmas de Linguagens de Programação - ClassificaçõesAdriano Teixeira de Souza
 
Linguagens de Programação
Linguagens de ProgramaçãoLinguagens de Programação
Linguagens de Programação12anogolega
 
Paradigmas de Programação - Imperativo, Orientado a Objetos e Funcional
Paradigmas de Programação - Imperativo, Orientado a Objetos e FuncionalParadigmas de Programação - Imperativo, Orientado a Objetos e Funcional
Paradigmas de Programação - Imperativo, Orientado a Objetos e FuncionalGustavo Coutinho
 
Linguagen..
Linguagen..Linguagen..
Linguagen..essa
 
Int. sistemas de informação iii
Int. sistemas de informação iiiInt. sistemas de informação iii
Int. sistemas de informação iiiRay Fran Pires
 
Introdução ao paradigma imperativo
Introdução ao paradigma imperativoIntrodução ao paradigma imperativo
Introdução ao paradigma imperativoTony Alexander Hild
 
Algoritmia para o site do 10gi marcelo e ricardo
Algoritmia para o site do 10gi marcelo e ricardoAlgoritmia para o site do 10gi marcelo e ricardo
Algoritmia para o site do 10gi marcelo e ricardozedaesquina98
 
Paradigmas de Linguagens de Programação - Introdução
Paradigmas de Linguagens de Programação - IntroduçãoParadigmas de Linguagens de Programação - Introdução
Paradigmas de Linguagens de Programação - IntroduçãoAdriano Teixeira de Souza
 
Apostila de Introdução aos Algoritmos - usando o Visualg
Apostila de Introdução aos Algoritmos - usando o VisualgApostila de Introdução aos Algoritmos - usando o Visualg
Apostila de Introdução aos Algoritmos - usando o VisualgRegis Magalhães
 
Fundamentos da linguagem c
Fundamentos da linguagem cFundamentos da linguagem c
Fundamentos da linguagem cMarcia Santana
 
Técnicas_Implementação
Técnicas_ImplementaçãoTécnicas_Implementação
Técnicas_ImplementaçãoWagner Zaparoli
 
Critérios de avaliação de linguagens
Critérios de avaliação de linguagensCritérios de avaliação de linguagens
Critérios de avaliação de linguagensPaulo Muniz
 
Introdução a Scala [GeekieTalk]
Introdução a Scala [GeekieTalk]Introdução a Scala [GeekieTalk]
Introdução a Scala [GeekieTalk]Nicolau Werneck
 

Mais procurados (20)

Linguagens de programação
Linguagens de programaçãoLinguagens de programação
Linguagens de programação
 
Software
SoftwareSoftware
Software
 
Paradigmas de Linguagens de Programação - Quatro Paradigmas + Ambientes de Pr...
Paradigmas de Linguagens de Programação - Quatro Paradigmas + Ambientes de Pr...Paradigmas de Linguagens de Programação - Quatro Paradigmas + Ambientes de Pr...
Paradigmas de Linguagens de Programação - Quatro Paradigmas + Ambientes de Pr...
 
Conceitos Iniciais de Linguagens de Programação
Conceitos Iniciais de Linguagens de ProgramaçãoConceitos Iniciais de Linguagens de Programação
Conceitos Iniciais de Linguagens de Programação
 
Paradigmas de Linguagens de Programação - Classificações
Paradigmas de Linguagens de Programação - ClassificaçõesParadigmas de Linguagens de Programação - Classificações
Paradigmas de Linguagens de Programação - Classificações
 
02 historia da programação
02   historia da programação02   historia da programação
02 historia da programação
 
Linguagens de Programação
Linguagens de ProgramaçãoLinguagens de Programação
Linguagens de Programação
 
Paradigmas de Programação - Imperativo, Orientado a Objetos e Funcional
Paradigmas de Programação - Imperativo, Orientado a Objetos e FuncionalParadigmas de Programação - Imperativo, Orientado a Objetos e Funcional
Paradigmas de Programação - Imperativo, Orientado a Objetos e Funcional
 
Linguagen..
Linguagen..Linguagen..
Linguagen..
 
Int. sistemas de informação iii
Int. sistemas de informação iiiInt. sistemas de informação iii
Int. sistemas de informação iii
 
Introdução ao paradigma imperativo
Introdução ao paradigma imperativoIntrodução ao paradigma imperativo
Introdução ao paradigma imperativo
 
Algoritmia para o site do 10gi marcelo e ricardo
Algoritmia para o site do 10gi marcelo e ricardoAlgoritmia para o site do 10gi marcelo e ricardo
Algoritmia para o site do 10gi marcelo e ricardo
 
Paradigmas de Linguagens de Programação - Introdução
Paradigmas de Linguagens de Programação - IntroduçãoParadigmas de Linguagens de Programação - Introdução
Paradigmas de Linguagens de Programação - Introdução
 
Apostila de Introdução aos Algoritmos - usando o Visualg
Apostila de Introdução aos Algoritmos - usando o VisualgApostila de Introdução aos Algoritmos - usando o Visualg
Apostila de Introdução aos Algoritmos - usando o Visualg
 
Aula 08-oac-execucao-de-programas
Aula 08-oac-execucao-de-programasAula 08-oac-execucao-de-programas
Aula 08-oac-execucao-de-programas
 
Fundamentos da linguagem c
Fundamentos da linguagem cFundamentos da linguagem c
Fundamentos da linguagem c
 
Técnicas_Implementação
Técnicas_ImplementaçãoTécnicas_Implementação
Técnicas_Implementação
 
Critérios de avaliação de linguagens
Critérios de avaliação de linguagensCritérios de avaliação de linguagens
Critérios de avaliação de linguagens
 
Introdução a Scala [GeekieTalk]
Introdução a Scala [GeekieTalk]Introdução a Scala [GeekieTalk]
Introdução a Scala [GeekieTalk]
 
01 aula1 habib
01 aula1 habib01 aula1 habib
01 aula1 habib
 

Semelhante a Paradigmas de Linguagens

Aula de C para Linux
Aula de C para LinuxAula de C para Linux
Aula de C para LinuxChris x-MS
 
Linguagens de programação 03-12-09
Linguagens de programação   03-12-09Linguagens de programação   03-12-09
Linguagens de programação 03-12-09essa
 
TREINAMENTO EM LOGICA DE PROGRAMAÇÃO (Sandra Rita) (z-lib.org).pdf
TREINAMENTO EM LOGICA DE PROGRAMAÇÃO (Sandra Rita) (z-lib.org).pdfTREINAMENTO EM LOGICA DE PROGRAMAÇÃO (Sandra Rita) (z-lib.org).pdf
TREINAMENTO EM LOGICA DE PROGRAMAÇÃO (Sandra Rita) (z-lib.org).pdfssuser059c2c1
 
Noções Básicas do Software dos Computadores Digitais
Noções Básicas do Software dos Computadores DigitaisNoções Básicas do Software dos Computadores Digitais
Noções Básicas do Software dos Computadores DigitaisHenry Raúl González Brito
 
Net uma revisão sobre a programação orientada a objetos
Net   uma revisão sobre a programação orientada a objetosNet   uma revisão sobre a programação orientada a objetos
Net uma revisão sobre a programação orientada a objetosLP Maquinas
 
Programação 1
Programação 1Programação 1
Programação 1essa
 
Módulo 9 - Introdução à Programação Orientada a Objectos
Módulo 9 - Introdução à Programação Orientada a Objectos Módulo 9 - Introdução à Programação Orientada a Objectos
Módulo 9 - Introdução à Programação Orientada a Objectos Luis Ferreira
 
C a linguagem de programação
C   a linguagem de programaçãoC   a linguagem de programação
C a linguagem de programaçãoAndrei Bastos
 
Apresentação final
Apresentação finalApresentação final
Apresentação finalvalmon
 

Semelhante a Paradigmas de Linguagens (20)

Cap02
Cap02Cap02
Cap02
 
Cap02
Cap02Cap02
Cap02
 
Cap02
Cap02Cap02
Cap02
 
Linguagem da programação
Linguagem da programaçãoLinguagem da programação
Linguagem da programação
 
Aula de C para Linux
Aula de C para LinuxAula de C para Linux
Aula de C para Linux
 
FC-Logic
FC-LogicFC-Logic
FC-Logic
 
Poo frank
Poo frankPoo frank
Poo frank
 
Linguagens de programação 03-12-09
Linguagens de programação   03-12-09Linguagens de programação   03-12-09
Linguagens de programação 03-12-09
 
Aula01 - Analise e Programação
Aula01 - Analise e ProgramaçãoAula01 - Analise e Programação
Aula01 - Analise e Programação
 
TREINAMENTO EM LOGICA DE PROGRAMAÇÃO (Sandra Rita) (z-lib.org).pdf
TREINAMENTO EM LOGICA DE PROGRAMAÇÃO (Sandra Rita) (z-lib.org).pdfTREINAMENTO EM LOGICA DE PROGRAMAÇÃO (Sandra Rita) (z-lib.org).pdf
TREINAMENTO EM LOGICA DE PROGRAMAÇÃO (Sandra Rita) (z-lib.org).pdf
 
Isc aula 7
Isc   aula 7Isc   aula 7
Isc aula 7
 
Noções Básicas do Software dos Computadores Digitais
Noções Básicas do Software dos Computadores DigitaisNoções Básicas do Software dos Computadores Digitais
Noções Básicas do Software dos Computadores Digitais
 
Net uma revisão sobre a programação orientada a objetos
Net   uma revisão sobre a programação orientada a objetosNet   uma revisão sobre a programação orientada a objetos
Net uma revisão sobre a programação orientada a objetos
 
Programação 1
Programação 1Programação 1
Programação 1
 
Aula 01 algoritmo
Aula 01 algoritmoAula 01 algoritmo
Aula 01 algoritmo
 
Módulo 9 - Introdução à Programação Orientada a Objectos
Módulo 9 - Introdução à Programação Orientada a Objectos Módulo 9 - Introdução à Programação Orientada a Objectos
Módulo 9 - Introdução à Programação Orientada a Objectos
 
C a linguagem de programação
C   a linguagem de programaçãoC   a linguagem de programação
C a linguagem de programação
 
Aplicativo aula01
Aplicativo aula01Aplicativo aula01
Aplicativo aula01
 
Estruturas de dados em c
Estruturas de dados em cEstruturas de dados em c
Estruturas de dados em c
 
Apresentação final
Apresentação finalApresentação final
Apresentação final
 

Paradigmas de Linguagens

  • 1. Prof. Kesede R Julio Apostila de Paradigmas de Linguagens de Programação versão: 2011
  • 2. 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
  • 3. 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
  • 4. 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
  • 5. 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
  • 6. 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ântico verifica se há conflitos difíceis/impossíveis de serem resolvidos pelo Parser. Ex.: coersão de tipos Gerador de código de máquina constroe um código capaz de ser interpretado pela CPU do computador (micro-instruções)
  • 7. 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
  • 8. 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
  • 9. 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
  • 10. 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
  • 11. 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
  • 12. 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
  • 13. 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
  • 14. 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
  • 15. 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
  • 16. 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
  • 17. 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
  • 18. 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
  • 19. 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
  • 20. 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
  • 21. 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
  • 22. 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
  • 23. 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
  • 24. 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
  • 25. 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
  • 26. 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
  • 27. 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
  • 28. 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
  • 29. 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
  • 30. 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
  • 31. 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
  • 32. 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
  • 33. 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
  • 34. 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
  • 35. 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
  • 36. 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
  • 37. 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
  • 38. 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
  • 39. 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
  • 40. 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
  • 41. 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
  • 42. 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
  • 43. 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
  • 44. 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 de forma decimal em vez de binária) Paradigmas de Linguagens de Programação - versão 2011 44
  • 45. 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
  • 46. 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
  • 47. 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
  • 48. 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
  • 49. 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
  • 50. 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
  • 51. 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 a Mais 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
  • 52. 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
  • 53. 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
  • 54. 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
  • 55. 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
  • 56. 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
  • 57. 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
  • 58. 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
  • 59. 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
  • 60. 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
  • 61. 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
  • 62. 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
  • 63. 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
  • 64. 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
  • 65. 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
  • 66. 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
  • 67. Prof. Kesede R Julio Paradigmas de Linguagens de Programação - versão 2011 67 N = 5 > 0 → OK F = ? 120 N1 = 4 is 5 - 1 → OK fatorial(4, F1 = ? 24) :- ... N = 4 > 0 → OK F = ? 24 N1 = 3 is 4 - 1 → OK fatorial(3, F1 = ? 6) :- ... N = 3 > 0 → OK F = ? 6 N1 = 2 is 3 - 1 → OK fatorial(2, F1 = ? 2) :- ... N = 2 > 0 → OK F = ? 2 N1 = 1 is 2 - 1 → OK fatorial(1, F1 = ? 1) :- ... N = 1 > 0 → OK F = ? 1 N1 = 0 is 1 - 1 → OK fatorial(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
  • 68. 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
  • 69. 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
  • 70. 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
  • 71. 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
  • 72. 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
  • 73. 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
  • 74. 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
  • 75. 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
  • 76. 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
  • 77. 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
  • 78. 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
  • 79. 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
  • 80. 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 kesedejulio@gmail.com Paradigmas de Linguagens de Programação - versão 2011 80