Paradigmas de Programação - IME-USP

Propaganda
PARADIGMAS DE PROGRAMAÇÃO
1º semestre de 2005
Profª Evelyn Cristina
Assunto: Linguagem Funcional
Características das Linguagens Imperativas
As linguagens imperativas (Pascal, C, entre outras) são caracterizadas por
três conceitos: variáveis, atribuições e laços de iteração. O estado de um
programa é mantido nas variáveis do programa. Essas variáveis são associadas a
localizações de memória caracterizadas por um endereço e por um valor
armazenado no endereço. Nós podemos acessar o valor diretamente, lendo o
valor da variável, ou indiretamente, através de ponteiros que levam ao valor da
variável.
O valor da variável é modificado através de atribuições. Por exemplo, em
Pascal:
X := 5;
O termo à esquerda do operador de atribuição (:=) é a variável cujo valor
está sendo modificado e à direita está o novo valor. X tem valores diferentes antes
e depois da atribuição. Portanto o significado (efeito) de um programa depende da
ordem em que as atribuições são escritas e executadas.
Já na Matemática, as variáveis são associadas a valores e, uma vez que o
fazem, elas não mudam mais seus valores. Portanto, o valor de uma função não
dependem de tais conceitos de ordem de execução. Ao invés disso, uma função
matemática define uma mapeamento de um valor do domínio para um valor do
conjunto imagem. É um conjunto de pares ordenados que relacionam unicamente
cada elemento do domínio ao seu elemento correspondente no conjunto imagem.
As funções em linguagens imperativas, por outro lado, são descritas como
algoritmos que especificam como computar uma faixa de valores de um domínio
com uma série pré-escrita de passos.
Uma última característica importante das linguagens imperativas é que a
repetição (laços) é utilizada extensivamente para computar valores desejados.
Laços são usados para varrer um vetor ou acumular valores numa variável. Em
contraste, nas funções matemáticas os valores são computados através de
aplicações de funções. Recursão é utilizada no lugar de iteração. Composição de
funções é também utilizada para compor funções mais poderosas.
Por causa dessas características, as linguagens imperativas têm sido
chamadas de orientadas a estado ou orientadas à atribuição. Em contraste, as
linguagens funcionais têm sido chamadas de baseadas em valores e aplicativas.
Funções Matemáticas
Uma função é uma regra de mapeamento (ou associação) de membros de
um conjunto (o conjunto domínio) a membros de um conjunto imagem. Por
exemplo, a função dobro mapeia um conjunto de números a naturais a um
subconjunto de números naturais.
O dobro de 0 é 0
O dobro de 1 é 2
O dobro de 2 é 4
O dobro de 3 é 6
O dobro de 4 é 8
f(x) = 2*x
Domínio={0, 1,2, 3, 4, ...} e Imagem={0, 2, 4, 6, 8, ...}
Uma vez que a função foi definida, ela pode ser aplicada a um elemento
particular do conjunto domínio: a aplicação leva à associação de um elemento no
conjunto imagem.
Se uma função F é definida em composição de duas outras G e H, ela é
escrita assim:
F  G o F,
Onde a aplicação de F é definida como a aplicação de H e então aplicar G
ao resultado de H.
Muitas funções matemáticas são definidas recursivamente, isto é, a
definição da função contém uma aplicação da própria função. Por exemplo:
n!1,=se {n=0
n*(n-1)!, se n>0
Domínio= N e Imagem=N
Ou seja, o fatorial de um número é definido na multiplicação desse número
pelo fatorial do número natural anterior.
Programação Recursiva
É possível fazer programas recursivos também em linguagens imperativas.
Por exemplo, abaixo está uma implementação da função fatorial em C.
int fatorial(int n){
if (n == 0)
return 1;
else
return ( n * fatorial( n – 1 ) );
}
Se num programa chamamos essa função da seguinte forma:
X := fatorial(4);
Então, ela será executada da seguinte forma:
N=4 -> 4 * fatorial(3)
N=3 -> 3 * fatorial(2)
N=2 -> 2* fatorial(1)
N=1 -> 1 * fatorial(0)
N=0 -> 1
Como temos o fatorial de 0, assim podemos calcular o fatorial de 1 (1 * 1 =
1). Uma vez já conhecido o fatorial de 1, obtemos facilmente o fatorial de 2 (2 * 1 =
2). Agora é possível computarmos o fatorial de 3( 3 * 2=6). E finalmente chegamos
ao fatorial de 4 (4 * 6=24). A função fatorial(4) retorna 24.
Outro exemplo:
int fibonacci (int n)
{
int s1, s2 ;
if (n == 0) return 1;
else if (n == 1) return 1;
else {
s1 = fibonacci(n-1);
s2 = fibonacci(n-2);
return s1 + s2;
}
}
Exercício: simule a execução recursiva da função fibonacci(6).
Princípios de Linguagens Funcionais
Uma linguagem de programação funcional tem três componentes principais:
1.
Um conjunto de dados. Tradicionalmente, as linguagens de
programação funcionais oferecem mecanismos de alto nível para
tratamento de estruturas como listas.
2.
Um conjunto de funções pré-construídas para manipular
objetos básicos de dados. Por exemplo, LISP e ML oferecem várias
funções para tratamento e construção de listas.
3.
Um conjunto de formas funcionais para construção de novas
funções. Um exemplo comum é a composição de funções.
Lambda Calculus
Lambda calculus é um cálculo que modela os aspectos computacionais das
funções. Estudar lambda calculus ajuda a entender os elementos da programação
funcional e semântica das linguagens de programação funcionais
independentemente dos detalhes de sintática de uma linguagem particular.
Lambda calculus leva esse nome devido à letra grega  (lambda), que é
usada na notação. As expressões do lambda calculus são de três tipos:
e1 – um expressão pode ser um identificador único como x, ou uma
constante como 3.
e2 – uma expressão pode ser uma definição de função. Tal expressão tem
a forma (x.e). A expressão e representa o corpo da função e x o parâmetro da
função. A função quadrado por exemplo, é definida assim: x.x*x.
e3 – uma expressão pode ser uma aplicação da função. Uma aplicação de
função tem a forma (e1 e2), onde a função e1 é aplicada à expressão e2. Por
exemplo, a função quadrado pode ser aplicada ao valor 2 dessa maneira: ((x.x*x)
2).
LISP
O LISP original, introduzido por John McCarthy em 1960 e conhecido como
LISP puro, é uma linguagem completamente funcional. Ela introduziu muitos
novos conceitos de linguagens, incluindo o tratamento uniforme de programas
como dados, expressões condicionais, coleta de lixo automática e execução
interativa de programas.
Objetos de dados
LISP é uma linguagem para computação simbólica. Valores são
representados por expressões simbólicas. Uma expressão pode ser um átomo ou
uma lista. Um átomo é uma cadeia de caracteres (letras, dígitos e outros). Os
seguintes são átomos:
A
AUSTRIA
68000
Uma lista é uma seqüência de átomos oulistas, separados por espaço e
parênteses. Os seguintes são listas:
(PLUS A B)
((CARNE FRANGO) (BROCOLIS BATATA TOMATE) AGUA)
Uma lista vazia é também chamada de NIL. A lista é o único mecanismo
para estruturar e codificar informação em LISP puro. Um símbolo (ou átomo) é
número ou um nome. Um número representa um valor diretamente. Um nome
representa um valor associado ao nome.
Há modos diferentes de associar um valor a um nome: SET associa um
valor globalmente e LET associa localmente. (SET X (A B C)) associa X a uma
lista (A B C).
Funções
Existem muito poucas funções primitivas oferecidas pelo LISP puro. A
função aspa simples (‘) dá o literal. Por exemplo, ‘A representa a letra A e não
uma variável A.
(CAR ‘(A B C)) retorna A, que é o cabeça da lista de literais (A B C).
(CDR ‘(A B C)) retorna a cauda da lista (A B C), que é (B C).
(CONS ‘A ‘(B C)) retorna a lista (A B C)
(CONS ‘(A B C) ‘(X Y Z)) retorna ((A B C) X Y Z)
A função ATOM returna T se o argumento é um átomo e () caso contrário.
NULL retorna T se seu argumento é NIL. EQ compare seus argumentos (que deve
ser átomos), se eles são iguais. Por exemplo:
(ATOM ‘A) = T
(ATOM ‘(A)) = NIL
(EQ ‘A ‘A) = T
(EQ ‘A ‘B) = NIL
A função COND serve como uma expressão “case”. Ela recebe como
argumento uma lista de pares . Ela é avaliada examinando os pares na seqüência,
para encontrar o primeiro par cujo predicado é avaliado como true. Exemplo:
(COND ((ATOM ‘(X)) ‘B) (T ‘C)) = C
A primeira condição é falsa porque X não é um átomo. A segunda condição
é true (T) e funciona como se fosse uma cláusula else.
A função x+y.x+y em LISP como
(LAMBDA (X Y) (PLUS X Y))
A aplicação de função também segue o modelo de expressão lambda. Por
exemplo:
((LAMBDA (X Y) (PLUS X Y)) 2 3)
associa X a 2 e Y a 3 e aplica PLUS (soma), resultando 5.
A associação de um nome a função é feita através da função DEFINE:
(DEFINE (ADICAO (LAMBDA (X Y) (PLUS X Y))))
Agora o átomo ADICAO pode ser usado no lugar da função. Se eu quiser
aplicar a função, agora posso fazer assim:
(ADICAO 2 3) resulta em 5.
Dar um nome à função é especialmente útil para definir funções recursivas.
Por exemplo, nós podemos definiar a função INVERTE para inverter os elementos
de uma lista:
(DEFINE (INVERTE (LAMBDA (L)
(INV NILL L))))
(DEFINE INV (LAMBDA (SAIDA ENTRADA)
(COND (NULL ENTRADA) SAIDA)
(T (INV (CONS (CAR ENTRADA) SAIDA) CDR ENTRADA)))))))
A função INVERTE chama a função INV, que trabalha pegando o primeiro
elemento da lista e chamando INV no resto da lista. Este programa demonstra
duas técnicas utilizadas em programação funcional. LISP aceita a chamada da
função INV na função INVERTE antes mesmo de a definirmos.
INV tem dois argumentos para computação e são chamados
recursivamente.Inicialmente, o segundo parâmetro contém o valor de entrada (a
lista a ser invertida) e o primeiro parâmetro contém um lista vazia. Cada chamada
a INV remove o primeiro elemento do segundo parâmetro e o insere no início da
lista do primeiro parâmetro.Quando o segundo parâmetro é exaurido, ou seja,
atinge lista vazia, então o primeiro parâmetro contém a lista invertida. O programa
termina, retornando a lista invertida.
Bibliografia

Ghezzi, Carlo; Jazayeri, Mehdi. Programming Language Concepts.
3ª ed. 1998. Ed. John Wiley & Sons.
Download