Você está na página 1de 35

INE 5363 – Programação Funcional – INE/CTC/UFSC – Bacharelado em Ciências da Computação

1. Introdução

1.1. Histórico

A elaboração de modelos de computação


(resolução de problemas por uma máquina)
baseia-se em trabalhos de dois pesquisadores
com enfoques bastante diferentes:
• Máquinas de Turing (Alan Turing, 1936)
• Cálculo Lambda (Alonzo Church, 1936)
Alan Turing apresentou em um artigo em 1936
um predicado formalmente exato para o predicado
informal e coloquial “pode ser calculado através
da utilização de um método efetivo”, as LCMs.
Este era o modelo matemático, descrito nas
seguintes palavras pelo próprio Turing: ‘LCMs
(logical computing machines) podem realizar tudo
o que puder ser descrito através de um conjunto
de “regras de bom senso” ou de “forma puramente
mecânica” ‘.
LCMs foram mais tarde chamadas de Máquinas
de Turing e assim são conhecidas até hoje. Alonzo Church
Uma Máquina de Turing genérica como descrita
originalmente consiste de uma um cabeçote de leitura/escrita que manipula dados contidos em uma
fita com instruções. A posição seguinte da fita a ser lida depende do 'estado atual' registrado. Para
nós esse “modelo computacional” parece uma coisa inefetiva, grosseira e possivelmente incapaz de
resolver problemas mais complexos do que adicionar dois números, mas era revolucionário para a
época e Turing provou que era de fato um modelo computacional universal.

Representação esquemática de uma Máquina de Turing

Prof. Dr. rer.nat. Aldo von Wangenheim http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 – Programação Funcional – INE/CTC/UFSC – Bacharelado em Ciências da Computação

Dessa forma, pergunta sobre a existência ou inexistência de um


método automático formal para a resolução de algum determinado
problema pôde acuradamente ser substituida pela pergunta se o
problema em questão é calculável ou não com uma Máquina de
Turing. Esta forma de descrever a relação entre problemas e seu
modelo, na verdade a Tese de Turing, foi apresentada no decorrer de
uma série de discusões sobre o Problema da Decisão
(Entscheidungsproblem) de um sistema de lógica simbólica. A
Entscheidbarkeit (Decidabilidade) de um sistema de lógica simbólica,
colocada por David Hilbert em 1928, é dada pelo problema de
encontrar-se um método efetivo através do qual, dada uma expressão
Q qualquer na notação deste sistema lógico simbólico, pode-se
determinar se Q é ou não é passível de se provada (como verdadeira
ou como falsa, tanto faz) pelo sistema. Hilbert inicialmente acreditava
que todo problema matematicamente formulável também fosse
decidível e Turing provou que ele estava errado. David Hilbert

O teste de tabela verdade para prova de tautologias é um método


desta categoria para o Cálculo Proposicional. Cálculo Proposicional logo representa uma classe de
Problemas Decidíveis. Para um pequeno número de variáveis você resolve o teste facilmente e nada
impede que se resolva para qualquer número de variáveis dado um estoque suficiente de papel e
lápis e uma vida longa. Turing mostrou que, dada a sua tese, não existe um método assim para o
Cálculo de Predicados.
Turing atacou este problema da seguinte forma: Suponha que você possua um algoritmo geral de
decisão para Lógica de Primeira Ordem. A decisão se uma máquina de Turing pára ou não pode ser
formulada como uma expressão de primeira ordem, a qual seria então suscetível ao algoritmo. Mas
Turing havia provado pouco antes que não existe um algoritmo geral que pode decidir se uma dada
Máquina de Turing pára ou não. Este é o Problema da Parada.
Alonzo Church, por sua vez, também em 1936, encontrou também uma resposta negativa para o
Entscheidungsproblem, mas de uma forma completamente diferente: Ele utilizou o conceito existente
na época de Lambda-definibilidade. Uma função de inteiros positivos é dita ser Lambda-definível se
os valores da função podem ser calculados através de um processo de substituições repetidas. Ele
provou que não existe um algoritmo definido através de funções recursivas capaz de decidir para
duas Expressões-Lambda se elas são equivalentes ou não. Para isso ele teve de inventar o Cálculo-
Lambda.
Alan Turing provou em 1937 a equivalência entre uma máquina de Turing e o Cálculo Lambda em
termos de computabilidade.
O resultado final desta troca de interações entre Turing e Church resultou no que é chamado de Tese
de Church-Turing, a qual trata da noção de um método efetivo ou mecânico em lógica e em
matemática. Efetividade e mecanicidade não possuem nesse contexto o significado coloquial, mas
sim um significado matemático. Pode-se dizer que M é um método efetivo e mecânico somente se:
1. M é descrito em termos de um conjunto finito de instruções exatas, cada qual por sua vez
expressa sob a forma de um número finito de símbolos;

2. M produzirá, se executado sem erros, o resultado desejado em um número finito de passos;

Prof. Dr. rer.nat. Aldo von Wangenheim http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 – Programação Funcional – INE/CTC/UFSC – Bacharelado em Ciências da Computação

3. M pode, na prática ou pelo menos em princípio, ser executado por um ser humano sem
nenhuma outra ajuda que um lápis e papel;

4. M não exige do ser humano que o executa nenhum brilhantismo, originalidade ou genialidade.

No fundo, isto nada mais é que adefinição matemática formal do conceito de algoritmo, realizada
primeiramente por Alonzo Church através da utilização de seu enfoque.
Detalhes sobre podem ser encontrados na Stanford Encyclopedia of Philosophy.
A conseqüência direta disso para nós mortais comuns, além de progressos na Teoria da Computação
dos quais usufruímos, é a de que o Cálculo Lambda serviu de base teórica para as Lingugens de
Programação Funcionais.
1.2. Linguagens Funcionais
• Interessantes pela sua simplicidade sintática
• Facilidade de descrever-se problemas recursivos.
• Muitas das implementações são poucos aceitas devido à ineficiência em comparação com
linguagens de programação „tradicionais“.
• Novas implementações de interpretadores/compiladores e novas linguagens mais modernas
tem surgido.
Exemplos:
• LISP (LISt Processing - década de 60). Muito simples em muitos aspectos. Atualmente é
ainda a mais utilizada.
• Miranda (Turner 1985)
• Haskell (1990)
• Orwell (Wadler 1985)
• Outras: ML, KRC, LML, SASL.

1.3. Utilidade do Cálculo Lambda em Programação Funcional:

• Ponto de Partida: Maioria da Linguagens de Programação Funcional são semelhantes e


diferem somente em aspectos sintáticos (S.P.Jones).
• Uma linguagem funcional de alto nível pode ser compilada em um código intermediário com
uma sintaxe e semântica extremamente simples. Essa linguagem é expressa em termos de
Cálculo Lambda.
• O Cálculo Lambda é suficientemente expressivo para permitir a codificação de uma linguagem
funcional de alto nível.
• Aloritmos para a resolução de problemas computacionais expressos em Cálculo Lambda
podem ser facilmente implementados de diferentes formas.

Prof. Dr. rer.nat. Aldo von Wangenheim http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 – Programação Funcional – INE/CTC/UFSC – Bacharelado em Ciências da Computação

Implementação de um Programa Funcional

O capítulo 2 desta disciplina vai se ocupar basicamente da notação-lambda e de problemas


relacionados à compilação de um programa funcional em termos de Cálculo Lambda.

2. Aspectos Teóricos Básicos: Cálculo Lambda


O Cálculo Lambda (calculo – λ) é uma coleção de diversos sistemas formais baseados em uma
notação para funções desenvolvida inicialmente por Alonzo Church em 1936.
É projetado para capturar os aspectos mais básicos da maneira pela qual operadores ou funções
podem ser combinados para formar outros operadores.
O Cálculo Lambda serve como uma ponte entre linguagens funcionais de alto nível e suas
implementações de baixo nível. Razões para a apresentação do Cálculo Lambda como uma
linguagem intermediária:
• É uma linguagem extremamente simples, consistindo de somente algumas poucas construções
sintáticas e de uma semântica simples.
Uma implementação do Cálculo Lambda necessita somente de suportar algumas construções
simples.
A sua semântica simples nos permite analisar facilmente a correção de sua implementação.
• É uma linguagem expressiva, a qual é suficientemente poderosa para expressar todos os
programas funcionais e, por conseguinte, todas as funções computáveis.
Isto significa que, se nós possuímos uma boa implementação do Cálculo Lambda, nós podemos
implementar qualquer linguagem funcional através da implementação de um compilador desta
para o Cálculo Lambda.

2.1. A Sintaxe do Cálculo Lambda


Um exemplo simples de uma expressão em Cálculo Lambda é:
(+ 4 5)

Prof. Dr. rer.nat. Aldo von Wangenheim http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 – Programação Funcional – INE/CTC/UFSC – Bacharelado em Ciências da Computação

Todas as aplicações de funções no Cálculo Lambda são escritas em notação prefixada. Assim a
função + precede os argumentos 4 e 5. Um exemplo um pouco mais complexo é:
(+ (* 5 6) (* 8 3))
Do ponto de vista de implementação, um programa funcional pode ser visto como uma expressão,
que é “executada“ através da sua avaliação.
A avaliação ocorre através da seleção repetida de uma expressão redutível (redex) e de sua
redução. Expressão redutível é aquela que pode ser avaliada imediatamente.
• No exemplo temos dois redexes: (* 5 6) e (* 8 3).
• A expressão inteira (+ (* 5 6) (* 8 3)) não é um redex, uma vez que a função +
necessita de ser aplicada a dois números para poder ser redutível.
• Através da escolha arbitrária de do primeiro redex para redução, podemos escrever:
(+ (* 5 6) (* 8 3)) -> (+ 30 (* 8 3))
onde -> é pronunciado „reduz para“.
• Agora há somente um redex (* 8 3), do qual resulta: (+ 30 24)
• Esta redução gera um novo redex, que reduz: (+ 30 24) -> 54

2.1.1. Aplicação de Funções e „Currying“


Em Cálculo Lambda, a aplicação de uma função tem um papel tão central, que pode ser denotada
por simples justaposição.
Assim escrevemos f x para denotar „a função f aplicada ao argumento x“.
Para expressar a aplicação de uma função a vários argumentos não utilizamos a notação intuitiva (f
(x,y)) e sim a altenativa de escrever:
((+ 3) 4)
• A expressão (+ 3) denota a “função + aplicada ao argumento 3“, cujo resultado é uma
“função aplicada ao valor 4“.
• Como em todas as linguagens funcionais, o Cálculo Lambda permite que uma função retorne
um função como resultado.
• Este dispositivo nos permite imaginar todas as funções como possuindo somente um
argumento. Esta notação foi introduzida por Schonfinkel em 1924 e utilizada amplamente nos
trabalhos de Curry, de onde provém a expressão „currying“.

2.1.2. Uso dos Parênteses


Convecionalmente em matemática omite-se parênteses redundantes para evitar tornar expressões
pouco legíveis. Ex.:
(ab) + ((2c)/d) pode ser escrita ab + 2c/d
A segunda forma é mais fácil de ser lida e a semântica não se altera. O perigo é que a segunda
expressão pode ser ambígua e resultados diferentes podem ser obtidos, dependendo de como se

Prof. Dr. rer.nat. Aldo von Wangenheim http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 – Programação Funcional – INE/CTC/UFSC – Bacharelado em Ciências da Computação

executa.
Isto pode ser evitado através do estabelecimento de convenções sobre a precedência de operadores
e de funções (exemplo: multiplicação liga de forma mais forte do que adição). As vezes, parênteses
não podem ser omitidos, como: (b + c)/a
Convenções similares são úteis no Cálculo Lambda.
Considere:
((+ 3) 2)
• Através do estabelecimento da convenção de que aplicação de funções associa à esquerda,
podemos escrever:
(+ 3 2)
ou até:
+32
Abreviações deste tipo foram feitas no exemplos anteriores.
• Um exemplo mais complexo é a expressão:
((f ((+ 4) 3)) (g x))
que pode ser escrita:
f (+ 4 3)

2.1.3. Funções Embutidas e Constantes


Funções embutidas como + não existem no Cálculo Lambda na sua forma mais pura. Para fins
práticos, uma extensão que as suporte é útil.
Estas incluem funções aritméticas (como +, -, *, /), constantes (como 0, 1,...), funções lógicas
(como AND, NOT, OR,...) e constantes lógicas (TRUE, FALSE).
Exemplos:
-5 4 -> 1
AND TRUE FALSE -> FALSE
Também incluímos uma função condicional, cujo valor é descrito pelas regras de redução abaixo:
IF TRUE E E -> E
t f t

IF FALSE E E -> E
t f f

Construtores de dados em Cálculo Lambda serão introduzidos inicialmente através da definição de


três funções embutidas: CONS, HEAD e TAIL (as quais se comportam exatamente como as funções
LISP: CONS, CAR e CDR). CONS constroe um objeto composto, o qual pode ser desmantelado com
HEAD e TAIL. A operação é descrita pelas seguites regras de redução:
HEAD (CONS a b) -> a
TAIL (CONS a b) -> b

Prof. Dr. rer.nat. Aldo von Wangenheim http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 – Programação Funcional – INE/CTC/UFSC – Bacharelado em Ciências da Computação

Além dessas funções, definiremos a constante NIL, o valor nulo.


A escolha exata de funções embutidas é arbitrária.

2.1.4. Abstrações Lambda


Abstrações lambda são construções em Cálculo Lambda que denotam funções novas, não
embutidas em um cálculo específico.
Exemplo:
(λ x . +x 1)
• O λ indica o início de uma função e é imediatamente seguido de uma variável.
• Depois segue um ponto “.“ seguido pelo corpo da função. A variável é chamada de parâmetro
formal e nós dizemos que o λ a liga.

O exemplo acima pode ser lido: „Aquela (λ) função de (x) a qual (.) adiciona x a 1 (+ x 1).
Uma abstração lambda sempre consiste dessas partes: o λ , o parâmetro formal, o . e o corpo. Uma
abstração lambda pode ser considerada similar a uma definição de função em uma linguagem de
programação convencional, como „C“:
inc ( x ) {
int x;
( return( x + 1); }
• O parâmetro formal da abstração lambda corresponde ao parâmetro formal da função.
• O corpo da abstração é antes uma expressão do que uma sequência de comandos.
Diferença: funções em linguagens convencionais necessitam de um nome como inc, enquanto
que em Cálculo Lambda funções são anônimas.
Sintaxe: O corpo de uma abstração se extende o tanto para a direita quanto for possível.
• Assim, na expressão:

(λ x . + x 1) 4

o corpo da abstração é (+ x 1) e não somente +.

Usualmente são incluídos parênteses a mais para clarificar a sintaxe:

(λ x . (+ x 1)) 4.

Uma abstração lambda sozinha dispensa quaisquer parênteses:

λ x . + x 1

Prof. Dr. rer.nat. Aldo von Wangenheim http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 – Programação Funcional – INE/CTC/UFSC – Bacharelado em Ciências da Computação

2.1.5. Sumário
λ
Definimos uma expressão lambda (expressão- ) como uma expressão no cálculo lambda.
A sintaxe de uma expressão lambda em notação BNF é dada abaixo.
<exp> ::= <constante> constantes embutidas
| <variável> nomes de variáveis
| <exp> <exp> aplicações

|
λ <variável> . <exp> abstrações lambda
ou, de forma mais estruturada (G.Revesz):
<exp>::= <constante>|<variável>|<aplicação>|<abstração>
<aplicação> ::= (<exp>) <exp>

<abstração> ::=
λ <variável> . <exp>
Esta sintaxe então nos permite formar expressões-λ como:
λ x.x λ x.λ y.(y)x λ x.(f)x
(f)3
λ f.(f)2 λ λ
( y.(x)y) x.(u)x

Resumindo a nomenclatura

Uma aplicação- λ é simplesmente a aplicação de uma expressão-


λ sobre outra. A primeira dessas
duas expressões-
λ é chamada de operador, a segunda de operando.
• Note que qualquer expressão-
λ pode ser utilizada tanto como operador como como operando.

Uma abstração-
λ é formada com o símbolo especial
λ seguido por uma variável, seguida por um
ponto, seguido por uma expressão-
λ arbitrária.
• O propósito da operação de abstração é o de formar uma expressão unária a partir de uma
expressão-
λ dada. A variável que ocorre próxima ao λ inicial dá nome ao argumento.
• Funções com mais de um argumento são formadas por abstrações repetidas.

Veja as nossas Referências Bibliográficas e Links em Cálculo Lambda.

Prof. Dr. rer.nat. Aldo von Wangenheim http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 - Programação Funcional - INE/CTC/UFSC - Curso de Bacharelado em Ciências da Computação

2.2. A Semântica Operacional do Cálculo Lambda

Até agora foi descrita a sintaxe do cálculo- λ . Para chamá-lo de “cálculo”, devemos porém dizer
como “calcular” com ele.

Basicamente isto é realizado através de três regras de conversão, que descrevem como conver-
ter uma expressão- λ em outra.

2.2.1. Introdução à conversão: Variáveis atadas e livres (bound/free)

Consideremos a expressão- λ : ( λ x. + x y) 4

Para avaliar esta expressão necessitamos:

• saber o valor “global” de y.


• não necessitamos saber o valor global de x, pois é o parâmetro formal da função.

• Assim vemos que: x e y possuem um status bastante diferente.

A razão é que x ocorre atado pelo λ x, é somente um encaixe dentro do qual o argumento 4 é
colocado quando a abstração- λ for aplicada ao argumento.

Por outr lado, y não é atado por nenhum λ e assim ocorre livre na expressão.

A ocorrência de uma variável é atada se há uma expressão- λ envolvente que a amarra, senão
é livre. No exemplo a seguir, x e y ocorrem atados, z porém, ocorre livre:

λ x. + (( λ y. + y z) 7) x
Observe que os termos atado e livre se referem a ocorrências específicas da variável em uma
expressão.

Uma variável pode possuir tanto uma ocorrência atada como uma livre em uma expressão.
Considere o exemplo:

+ x (( λ x. + x 1) 4)

Aqui x ocorre livre (a primeira vez) e atada (a segunda). Cada ocorrência individual de uma
variável deve ser ou atada ou livre.

Prof. Dr. rer.nat. Aldo von Wangenheim - http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 - Programação Funcional - INE/CTC/UFSC - Curso de Bacharelado em Ciências da Computação

As definições formais de livre a atado são dadas abaixo:

Definição de ocorre livre:

x ocorre livre em x (mas não em outra variável ou constante qualquer)

x ocorre livre em (E F) ⇔ x ocorre livre em E ou


x ocorre livre em F

x ocorre livre em λ y.E ⇔ x e y são variáveis diferentes e


x ocorre livre em E

Nota: nenhuma variável oorre atada em uma expressão consistindo e uma única constante.
Definição de ocorre atada:

x ocorre atada em (E F) ⇔ x ocorre atada em E ou


x ocorre atada em F

x ocorre atada em λ y.E ⇔ (x e y são a mesma variável e x ocorre livre em


E) ou
x ocorre atada em E

2.2.2. Conversão-Beta

Uma abstração- λ denota uma função. Assim necessitamos descrever como aplicá-la a um
argumento.

A função ( λ x. + x 1) 4 é a justaposição do função ( λ x. + x y) e do argumento 4 e, por conse-


guinte, denota a aplicação de uma certa função, denotada pela abstração- λ , ao argumento 4.

A regra para a aplicação de uma função é muito simples:

• O resultado da aplicação de uma abstração- λ um argumento é uma instância do corpo da


abstração- λ no qual ocorrências (livres) do parâmetro formal no corpo são repostos pelo
argumento.

O resultado de se aplicar a abstração ao argumento é: + 4 1 onde o (+ 4 1) é um instância do


corpo (+ x 1) no qual ocorrências do parâmetro formal x foram repostas pelo argumento 4.

Nós escrevemos as conversões utilizando o símbolo → como antes:

Prof. Dr. rer.nat. Aldo von Wangenheim - http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 - Programação Funcional - INE/CTC/UFSC - Curso de Bacharelado em Ciências da Computação

( λ x. + x 1) 4 → +41

Esta operação é chamada de β -redução e a sua implementação eficiente é um aspecto


importante na elaboração de linguagens de programação funcional.

Exemplos simpes de β -redução:


• O parâmetro formal pode ocorrer várias vezes no corpo:

( λ x. + x x) → +55
→ 10

Da mesma forma, poderá não haver ocorreêncuias do parâmetro formal no corpo: ( λ x. 3)


5 → 3

Nesse caso não há ocorrências do parâmetro formal (x), pelo qual o argumento 5 poderia ser
substituído. Assim o argumento é descartado sem uso.

O corpo de uma abstração λ poderá ser constituído por outra abstração λ:

( λ x. ( λ y. - y x)) 4 5 → ( λ y. - y 4) 5
→ -54
→ 1

Observe-se que, ao se construir a instância do corpo da abstração λ x, o corpo inteiro inclusive


a abstração λ y envolvida por esta, substituindo-se x, é copiado.

Aqui se observa também o efeito do Currying: a aplicação da abstração λ x retornou uma fun-
ção (a abstração λ y) como seu resultado. Esta por sua vez, aplicada resultou em ( - 5 4).
Funções encapsuladas umas dentro das outras são comumente abreviadas:

( λ x. ( λ y. E)) para ( λ x. λ y. E)

Funções podem ser argumentos também:

( λ f. f 3)( λ x. + x 1) → ( λ x. + x 1) 3
→ +31

Prof. Dr. rer.nat. Aldo von Wangenheim - http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 - Programação Funcional - INE/CTC/UFSC - Curso de Bacharelado em Ciências da Computação

→ 4

Uma instância da abstração λx é substituída por f onde quer que f apareça no corpo da
abstração de λ f.
Nomenclatura:

Nomes de parâmetros formais podem não ser únicos:

( λ x.( λ x. + ( - x 1)) x 3) 9 → ( λ x. + (- x 1)) 9 3


→ + (- 9 1) 3
→ 11
Observe-se que o x interno não foi substituído na primeira redução, pois estava protegido pelo
λ x envolvente, ou seja, a ocorrência interna de x não é livre no corpo da abstração
λ x externa.

Dada uma asbtração- λ , ( λ x.E), é possível identificar-se exatamente as ocorrências de x que


deveriam ser substitídas atraves da identificação de todas as ocorrências de x que estão livres.

Dessa forma, para o exemplo acima, examinamos o corpo da abstração:

( λ x. + (- x 1)) x 3

e vemos que a segunda ocorrência de x é livre, podendo ser substituída.

A idéia do aninhamento do escopo de variáveis em uma linguagem de programação estruturada


é análoga a esta regra.

Outro exemplo:

( λ x. λ y.+ x (( λ x.- x 3) y)) 5 6


→ ( λ y.+ 5 (( λ x.- x 3) y)) 6
→ + 5 (( λ x.- x 3) 6)
→ + 5 (- 6 3)
→ 8
Novamente, o x mais interno não é substituído, uma vez que não é livre no corpo da abstração
mais externa.

Prof. Dr. rer.nat. Aldo von Wangenheim - http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 - Programação Funcional - INE/CTC/UFSC - Curso de Bacharelado em Ciências da Computação

Exemplo maior: Modelagem de Construtores de Dados

Definimos CONS, HEAD e TAIL da seguinte forma:

CONS = ( λ a. λ b. λ f.f a b)
HEAD = ( λ c.c ( λ a. λ b.a))
TAIL = ( λ c.c ( λ a. λ b.b))

as fórmulas acima obedecem às regras para CONS, HEAD e TAIL definidas anteriormente
(Seção 2.1.3). Exemplo:

HEAD (CONS p q) = ( λ c.c ( λ a. λ b.a))(CONS p q)


→ CONS p q ( λ a. λ b.a)
= ( λ a. λ b. λ f.f a b) p q ( λ a. λ b.a)
→ ( λ b. λ f.f p b)q ( λ a. λ b.a)
→ ( λ f.f p q)( λ a. λ b.a)
→ ( λ a. λ b.a)p q
→ ( λ b p) q
→ p

Isto significa:
• Não existe necessidade real para as funções embutidas HEAD e CONS ou TAIL.
• Todas as funções embutidas podem ser modeladas como abstrações-lambda.
• De um ponto de vista teórico, isto é satisfatório, para efeitos práticos porém, não é utilizado
(eficiência).

2.2.3. Conversão-Alfa

Considere:

( λ x. + x 1) e ( λ y. + y 1)

Evidentemente elas devem ser equivalentes. A conversão- α é o nome dado à operação de


mudança de nome (consistente) de um parâmetro formal.

Notação:

( λ x. + x 1) ↔ ( λ y. + y 1)
α

Prof. Dr. rer.nat. Aldo von Wangenheim - http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 - Programação Funcional - INE/CTC/UFSC - Curso de Bacharelado em Ciências da Computação

α -congruência: duas expressões- λ M e N são α -congruentes (ou α -iguais), denotado por


M ≅ N se ou M ≡ N ou M ↔ N, ou N é obtido de M através da reposição de uma subx-
α
pressão S de M por uma expressão- λ T tal que S ↔ T, ou existe alguma expressão- λ R tal
α
que M ≅ ReR ≅ N.

2.2.4. Conversão-Eta

Sejam: ( λ x. + 1 x) e (+ 1). Estas expressões se comportam exatamente da mesma maneira,


quando aplicadas a um argumento: ambas adicionam 1 ao argumento.

Conversão- η é o nome dado à regra que expressa essa equivalência:

( λ x. + 1 x) ↔ (+ 1)
η

De forma mais geral, a regra da conversão- η pode ser expressa assim:

( λ x. F x) ↔ F
η

desde que x não ocorra livre em F e F denote uma função.

A condição de que x não deve ocorrer livre em em F previneconversões errôneas. Exemplo:

( λ x. + x x) não é η -convertível para (+ x)

pois x ocorre livre em (+ x).

A condição de que F deve denotar uma função previne outras conversões errôneas envovlendo
constantes embutidas (predefinidas). EXemplo:

TRUE não é η -convertível para ( λ x. TRUE x)

Quando a conversão- η é utilizada da esquerda para a direita, é chamada de redução- η .

2.2.5. Provas de Interconvertibilidade


• Freqüentemente será necessário poder-se demonstrar a interconvertibilidade de duas
expressões- λ .

Prof. Dr. rer.nat. Aldo von Wangenheim - http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 - Programação Funcional - INE/CTC/UFSC - Curso de Bacharelado em Ciências da Computação

• Quando as duas expressões denotam funções, esta prova pode se tornar extremamente
longa.

Há porém, métodos para abreviar provas, que não sacrificam o seu rigor. Exemplo:

IF TRUE (( λ p.p) 3) e ( λ x. 3)

Ambas as expressões denotam a mesma função, que invariavelmente retorna o valor 3, não
importa o valor de seu argumento e seria de se esperar que ambas sejam interconvertíveis.

Isto realmente ocorre:

IF TRUE (( λ p.p) 3) ↔ IF TRUE 3


β

↔ ( λ x. IF TRUE 3 x)
η
→ ( λ x. 3)

onde o passo final é a regra de redução para IF.

Um método alternativo de se provar a interconvertibilidade de duas expressões, muito mais con-


veniente, é o de se aplicar as duas expressões a um argumento w, como:

IF TRUE (( λ p.p) 3) w ( λ x. 3) w
→ ( λ p.p) 3 → 3
→ 3

Portanto: IF TRUE (( λ p.p) 3) ↔ ( λ x. 3)

Esta prova tem a vantagem de somente utilizar a redução e de evitar o uso explícito da conver-
são- η .

O raciocínio por detrás da generalização da interconvertibilidade acima segue abaixo. Suponha


que possamos demonstrar que:

F1 w → E

F2 w → E

Prof. Dr. rer.nat. Aldo von Wangenheim - http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 - Programação Funcional - INE/CTC/UFSC - Curso de Bacharelado em Ciências da Computação

onde w é uma variável que não ocorre livre tampouco em F1 como em F2, e E é alguma expres-
são, então podemos raciocinar da seguinte forma:

F1 ↔ ( λ w. F1 w)
η
↔ ( λ w. E)
↔ ( λ w. F2 w)

↔ F2
η

e por conseguinte F1 ↔ F2.

Não é sempre que expressões- λ que “deveriam” dizer a mesma coisa são interconvertíveis.
Isto será tratado mais tarde.

2.2.6. Sumário: Regras de Conversão

Há três regras de conversão que possibilitam a interconversão de expressões envolvendo


abstrações- λ :

1. Mudança de nome: a conversão- α permite que se troque o nome de parâmetros formais


de uma abstração- λ , penquanto isto for feito de forma consistente.

2. Aplicação de funções: A redução-β permite a aplicação de abstrações- λ a um argumen-


toatravés de geração de uma nova instância do corpo da abstração, substituindo o argu-
mento por ocorrências livres do parâmetro formal.
Cuidado especial deve ser tomado quando o argumento contém variáveis livres.

3. Eliminação de abstrações- λ redundantes: A redução- η pode as vezes eliminar uma


abstração- λ .

Dentro deste contexto, podemos considerar as funções embutidas (predefinidas) como mais
uma forma de conversão. Esta forma de conversão recebe o nome de conversão- δ .

Prof. Dr. rer.nat. Aldo von Wangenheim - http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 - Programação Funcional - INE/CTC/UFSC - Curso de Bacharelado em Ciências da Computação

2.3. Ordem de Redução


Se uma expressão não contém mais redexes, então a avaliação está completa. Um expressão
nesta forma é dita estar na forma normal.
Assim, a avaliação de uma expressão consiste na na redução sucessiva de redexes, até que a
expressão esteja na forma normal.
Definição (forma normal): uma expressão-l é dita estar na forma normal, se nenhum redex-b,
isto é, nenhuma subexpressão da forma (λx.P)Q ocorre nela.
Uma expressão pode conter mais do que um redex, assim a redução pode acontecer por
caminhos diferentes.
² Exemplo:
(+ (* 3 4) (* 7 8))
pode ser reduzido à forma normal pelas seguintes seqüências:
(+ (* 3 4) (* 7 8))
-> (+ 12 (* 7 8))
-> (+ 12 56)
-> 68
ou
(+ (* 3 4) (* 7 8))
-> (+ (* 3 4) 56)
-> ( 12 56)
-> 68
² Nem toda expressão possue uma forma normal,
considere:
(D D)
² onde D é (λx.x x).
A avaliação desta expressão jamais terminaria, uma vez que
(D D) reduz para (D D):
(λx.x x)(λx.x x) -> (λx.x x)(λx.x x)
-> (λx.x x)(λx.x x)
² Por conseguinte, algumas seqüências de redução poderão atingir a forma normal,
outras não.
Considere:
(λx.3) (D D)
²Se nós primeiro reduzirmos a aplicação de (λx.3) a (D D) (sem avaliar (D D)),
obteremos o resultado 3; porém se primeiro reduzimos a aplicação de D sobre D, nós
simplemente obtemos (D D) novamente.
² Se nós continuamos insistindo em escolher a aplicação de D sobre D, a avaliação vai
continuar indefinidamente, não terminando.
² Esta situação corresponde à de um programa imperativo que entra em um laço infinito.
Dizemos que a avaliação ou execução não termina.
² Para contornar esta situação, existe a ordem normal de redução, que garante que uma
forma normal, caso exista, será encontrada.

Prof. Dr. rer.nat. Aldo von Wangenheim - http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 - Programação Funcional - INE/CTC/UFSC - Curso de Bacharelado em Ciências da Computação

2.3.1. Ordem Normal de Redução


² Uma questão que se coloca, além da questão "poderei encontrar uma forma normal ?", é
a questão de se existe mais de uma forma normal para para uma expressão, ou:

Poderá uma seqüência de redução diferente levar a uma forma normal diferente?

Ambas as questões estão interligadas.


² A resposta para a última pergunta é: não.
Isto é uma conseqüência dos teoremas de Church-Rosser CRT1 e CRT2.

Teorema de Church-Rosser 1 (CRT1):


Se E1 <-> E2, então existe uma expressão E, tal que E1 -> E e E2 -> E
Corolário: Nenhuma expressão pode ser convertida em duas formas normais distintas. Isto
significa que não existem duas formas normais para uma expressão que não sejam a-
convertíveis entre si.
Prova: Suponha que E1 <-> E e E2 <-> E , onde E1 e E2 estão na forma normal.
Então E1 <-> E2 e, pelo CRT1, deve haver uma expressão F tal que E1 -> F e E2 -> F.
Como tanto E1 como E2 não possuem redexes, logo E1 = F = E2
² Informalmente, o teorema CRT1 diz que todas as seqüências de redução que terminam,
haverão de atingir o mesmo resultao.

O segundo teorema de Church-Rosser, CRT2, diz respeito a uma ordem particular de


redução, chamada ordem normal de redução.

Teorema de Church-Rosser 2 (CRT2):


Se E1 -> E2, e E2 está na forma normal, então existe uma ordem normal de seqüência
de redução de E1 para E2.
Conseqüências:
² Existe no mínimo um resultado possível e
² a ordem normal de redução encontrará este resultado, caso ele exista.
² Observe que nenhuma seqüência de redução poderá levar a um resultado incorreto, o
máximo que poderá acontecer é a não-terminação.
A Ordem Normal de Redução especifica que o redex mais à
esquerda mais externo deverá ser reduzido primeiro.
²No exemplo anterior (λx.3) (D D), escolheríamos o redex-λx primeiro, não o (D D).
² Esta regra encorpa a intuição de que argumentos para funções podem ser descartados,
de forma que deveríamos aplicar a função (lx.3) primeiro, ao invés de primeiro
avaliarmos o argumento (D D).

Prof. Dr. rer.nat. Aldo von Wangenheim - http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 - Programação Funcional - INE/CTC/UFSC - Curso de Bacharelado em Ciências da Computação

2.3.2. Ordens de redução ótimas


² Enquanto a ordem normal de redução garante que ou uma solução seja encontrada ou o
não-término ocorra, ela não garante que isto ocorra no número mínimo de passos de
redução, o que é um fato relevante na utilização do cálculo-l para a implementação de
linguagens de programação interpretadas.
² No caso da implementação da resolução através de redução de grafos - que
estudaremos mais tarde - porém, aparentemente a ordem normal de redução é
"geralmente ótima" em questão de tempo de execução.

Analisar uma expressão para encontrar o redex ótimo para ser reduzido é
aparentemente muito mais trabalhoso e gasta mais tempo do que arriscar uma redução
com mais passos seguindo "cegamente" a ordem normal.
² Alguns autores como (Levy, J.J.; Optimal Reductions in the Lambda Calculus in Essays
on Combinatory Logic, Academic Press, 1980) se ocuparam de algoritmos para
encontrar ordens ótimas ou quase-ótimas de redução que preservassem as qualidades
da ordem normal de redução. Isto é assunto de um tópico avançado que não cabe nesta
disciplina.

2.4. Funções Recursivas


² Se o propósito da utilização de cálculo lambda é a conversão de programas funcionais
neste para que possamos resolvê-los, devemos ser capazes de representar uma das
características mais marcantes das linguagens funcionais, a recursão.
² O cálculo lambda suporta a representação de recursividade sem maiores extensões
através da utilização de alguns pequenos truques.

2.4.1. Funções Recursivas e o Y


² Considere a definição da função fatorial abaixo:
FAC = (λn.IF (= n 0) 1 (* n (FAC (- n 1))))
² Esta definição baseia-se na capacididade de se dar um nome a uma abstração lambda e
de fazer referência a esta dentro dela mesma.
² Nenhuma construção deste tipo é provida pelo cálculo lambda.
² O maior problema aqui é que funções lambda são anônimas.
² Dessa forma elas não podem nomear-se e assim não podem se referenciar a si mesmas.

Para resolver este problema iniciamos com um caso onde encontramos a recursão na sua
forma mais pura:
FAC = (λn. ... FAC ... )
² Aplicando uma abstração-b sobre FAC, podemos transformar esta definição em:
FAC = (λfac.(λn. (...fac...)))FAC
² Esta definição podemos escrever da forma:
FAC = H FAC (2.1)
² onde:

Prof. Dr. rer.nat. Aldo von Wangenheim - http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 - Programação Funcional - INE/CTC/UFSC - Curso de Bacharelado em Ciências da Computação

H = (λfac.(λn. (...fac...)))
² A definição de H é trivial.
É uma abstração lambda ordinária e não usa recursão.
² A recursão é expressa somente pela definição 2.1.
A definição 2.1 pode ser encarada mais como uma equação matemática. Por exemplo, para
resolver a equação matemática
x2 - 2 = x
nós procuramos por valores de x que satisfazem a equação (neste caso x = -1 e x = 2) .
De forma similar, para resolver 2.1, nós procuramos uma expressão lambda para FAC que
satisfaça 2.1.
A equação 2.1 FAC = H FAC postula que, quando a função H é aplicada a FAC, o resultado
é FAC.
Nós dizemos que FAC é um ponto fixo de H.
Uma função pode ter mais de um ponto fixo. Por exemplo, na função abaixo, tanto 0 quanto
1 são pontos fixos da função:
λx. * x x
a qual calcula o quadrado de seu argumento.
Em resumo, estamos procurando por um ponto fixo para H. Claramente isto depende
somente de H.
Para isto, invente-se, a título provisório, uma função Y, a qual toma uma função como
argumento e devolve o seu ponto fixo como resultado.
Assim Y tem o comportamento de:
Y H = H (Y H)
e Y é chamado de combinador de ponto fixo.
Assim, caso possamos produzir um Y, temos uma solução para o problema da recursividade.
Para 2.1 podemos prover a seguinte solução:
FAC = Y H
a qual é uma definição não-recursiva de FAC.
Como teste para esta estratégia, podemos computar o valor de (FAC 1). Tomemos as
definições de FAC e H:
FAC = Y H
H = (λfac.λn. IF (= n 0) 1 (* n (fac (- n 1))))
Assim: FAC 1
-> Y H 1
-> H (Y H) 1
->(lfac.ln.IF (= n 0) 1 (* n (fac (- n 1))))(Y H) 1
->(ln.IF (= n 0) 1 (* n (Y H (- n 1)))) 1
-> IF (= 1 0) 1 (* 1 (Y H (- 1 1)))
-> * 1 (Y H 0)
= * 1 (H (Y H) 0)
= * 1 ((lfac.ln.IF (= n 0) 1 (* n (fac (- n 1))))(Y H) 0)
-> * 1 ((ln.IF (= n 0) 1 (* n (Y H(- n 1)))) 0)

Prof. Dr. rer.nat. Aldo von Wangenheim - http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 - Programação Funcional - INE/CTC/UFSC - Curso de Bacharelado em Ciências da Computação

-> * 1 (IF (= 0 0) 1 (* n (Y H(- 0 1))))


-> * 1 1
-> 1

2.4.2. Y pode ser definido como uma Abstração Lambda


² Para a transformação de uma expressão recursiva em uma não-recursiva, utilizamos o
combinador de ponto fixo, uma função que chamamos de Y.
² A propriedade que Y necessita ter é: Y H = H (Y H)
² e isto representa evidentemente a recursão da forma mais pura, uma vez que esta
fórmula pode ser utilizada para representar, de forma abstrata, qualquer fórmul
arecursiva que queiramos.
² O truque é que podemos representar Y como uma abstração lambda sem utilizar a
recursão:
Y = (λh. (λx.h (x x)) (λx. h (x x)))

Para demonstrar que Y possui a qualidade desejada, evaluemos:


Y H
= (λh. (λx.h (x x)) (λx. h (x x))) H
<-> (λx.H (x x)) (λx. H (x x))
<-> H ((λx.H (x x)) (λx. H (x x)))
<-> H (Y H)
² O fato de Y poder ser definido como uma abstração lambda é, do ponto de vista
matemático, realmente digno de nota.
² Do ponto de vista da implementação, é relativamente ineficiente implementar Y como
uma abstração lambda. Para a construção de linguagens funcionais, utiliza-se
geralmente uma função embutida com a regra de redução:
Y H -> H (Y H)

Prof. Dr. rer.nat. Aldo von Wangenheim - http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 - Programação Funcional - INE/CTC/UFSC - Curso de Bacharelado em Ciências da Computação

2.4.3. Exercícios (entregar próxima aula)


I. Redução:
Reduza as seguintes expressões-lambda às suas respectivas formas normais:
(((λf.λx.λy.(x)(f)y)p)q)r
(((λx.λy.λz.(y)x)(x)y)(u)z)y
(λx.(λy.(x)(y)y) λz.(x)(z)(λu.λv.u)w
(((λx.λy.λz.((x)z)(y)z)(λu.λv.u)w)λs.s)t
(((λx.(λz.(x)(y)y)ly.(x)(y)y)λz.λu.λv.(u)(z)v)(λr.λs.r)t)w
(λx.x(xy))N
(λx.y)N
(λx.(λy.xy)N)M
(λx.xx)(λx.xx)
(λx.xxy)(λx.xxy)
(λx.z)((λx.xxy)((λx.xxy))

II. Recursividade:
1. Dê uma definição recursiva para o maior divisor comum de dois inteiros e calcule o valor
de ((mdc) 10) 14) utilizando o combinador Y.
2. Tente o mesmo para os números de Fibonacci e use Y para computar (Fibo) 5.

Prof. Dr. rer.nat. Aldo von Wangenheim - http://www.inf.ufsc.br/~awangenh/Funcional


INE 5363 - Programação Funcional - Transparência 47

2.4.4. Resolução de alguns problemas de redução:

1. Redução direta:
(λx.x(xy))N-> N(Ny)

aqui N é substituído nos dois x, pois x está livre na subespressão


x(xy).

2. Redução direta também:


(λx.y)N -> y

3. Menos trivial:
(λx.(λy.xy)N)M -> (λy.My)N
pois x está livre em (λy.xy)N
-> MN

4. Exemplo simples que não termina:


(λx.xx)(λx.xx) -> (λx.xx)(λx.xx)
-> (λx.xx)(λx.xx) ...etc

Prof. Dr. Aldo von Wangenheim


INE 5363 - Programação Funcional - Transparência 48

5. Exemplo catastrofal:
(λx.xxy)(λx.xxy)-> (λx.xxy)(λx.xxy)y
-> (λx.xxy)(λx.xxy)yy
-> (λx.xxy)(λx.xxy)yyy
-> (λx.xxy)(λx.xxy)yyyy

6. Exemplo que pode ser simples


(λx.z)((λx.xxy)((λx.xxy)) -> z

7. O mesmo exemplo, quando feito de forma errada:


(λx.z)((λx.xxy)(λx.xxy)) ->(λx.z)((λx.xxy)(λx.xxy)y)
->(λx.z)((λx.xxy)(λx.xxy)yy)

Aqui, ao invés de reduzir o redex mais à esquerda, foi


aplicado(λx.xxy) sobre (λx.xxy)

Prof. Dr. Aldo von Wangenheim


INE 5363 - Programação Funcional - Transparência 49

2.4.5. Resolução do problema recursivo com a série de Fibonacci:

Relembrando: Recursão é um conceio fundamental em matemática e ciência da


computação. Uma definição simples de recursão é a de que um programa recursivo é
um que chama a si mesmo. UMa função recursiva é uma que é definida em função de
si mesma, daí o termo recursivo.

Os números de Fibonacci são uma relação de recorrência muito conhecida e utilizada


na matemática. Os números de Fibonacci são definidos da seguinte forma:

FN = FN-1 + Fn-2 para N >= 2, com F0 = F1 = 1

definindo a série:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233..
para os números:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12..
e o algoritmo é bem simples:

fibonacci ( n )
(
se n <= 1
retorne 1
senão
retorne ( fibonacci (n-1) + fibonacci (n-2) )
)

isto, expresso em uma função recursiva simples, fica:

FIBO = (λn.IF (<= n 1) 1 (+ (FIBO(- n 1))(FIBO(- n 2))))

Prof. Dr. Aldo von Wangenheim


INE 5363 - Programação Funcional - Transparência 50

• Da mesma forma como fizemos com o fatorial FAC, aplicando


uma abstração-β sobre FIBO, podemos transformar esta defini-
ção em:
FIBO = (λfibo.(λn. (...fibo...fibo...)))FIBO

• Esta definição podemos escrever então também da forma:


FIBO = H FIBO
• Usando Y:
FIBO = Y H
H=(λfibo.λn.IF (<= n 1) 1 (+ (fibo(- n 1))(fibo(- n 2))))

Prof. Dr. Aldo von Wangenheim


INE 5363 - Programação Funcional - Transparência 51

2.5. A Semântica Denotacional do Cálculo Lambda

•  Denotar (Aurélio): (do latim denotare)


Significar, exprimir, simbolizar

Há duas maneiras de se olhar para uma função:


• como um algoritmo que irá produzir um valor, dado um argu-
mento, ou
• como um conjunto de pares ordenados argumento-valor.

 O primeiro enfoque é dinâmico ou operacional, já que vê


uma função como uma seqüência de operações no tempo.

 O segundo enfoque é estático ou denotacional: a função


é encarada como um conjunto fixo de associações entre
argumentos e seus valores de função correspondentes.

Nos capitulos anteriores vimos como uma expressão ser avaliada


pel aaplicação repetida de regras de redução.

Essas regras descrevem somente transformações sintáticas em


expressões permitidas, sem fazer referência a o que essas fun-
ções significam. O cálculo λ pode ser considerado como um
sistema formal para a manipulação de símbolos sintáticos.

O desenvolvimento das regras de conversão foi baseado em nos-


sas intuições sobre funções abstratas e nos proveu uma semân-
tica operacional para o cálculo λ.

Ficou em aberto, uma definição de significado.

Prof. Dr. Aldo von Wangenheim


INE 5363 - Programação Funcional - Transparência 52

2.5.1. A Função Eval

O propósito da semântica denotacional de uma linguagem qual-


quer é o de atribuir um valor a toda expressão nesta linguagem.

Uma expressão é um objeto sintático, formado de acordo com as


regras de sintaxe desta linguagem, o que não diz nada sobre o
seu significado.

Um valor, em contraste, é um objeto matemático abstrato , como


“o número 5” ou “a função que quadra o seu argumento”.

Nós podemos, por conseguinte, expressar a semântica de uma lin-


guagem como uma função matemática Eval, de expressões para
valores:
Eval
Expressão Valor

Podemos agora escrever equações da seguinte forma:


Eval[[ + 3 4 ]] = 7

Isto diz “o significado (i.é. o valor) da expressão (+ 3 4) é o valor


numérico abstrato 7”.

Usamos colchetes duplos em negrito para fechar o argumento a


Eval para enfatizar que é um objeto sintático. Esta convenção é
muito utilizada na descrição de outras semânticas denotacionais.

Consideraremos a expressão (+ 3 4) como uma representação ou


denotação do valor 7.
• Daí o termo semântica denotacional.

Prof. Dr. Aldo von Wangenheim


INE 5363 - Programação Funcional - Transparência 53

Agora, um breve desenvovlimento informal da função Eval:


• O objetivo da função é o de provêr um valor para Eval[[ E ]] para
toda e qualquer expressão lambda E.
• Para atingir este objetivo podemos utilizar todos os recursos da
sintaxe do cálculo lambda já vistos até agora.

Supponhamos, primeiramente, que E seja uma variável x.


Que valor deveria Eval[[ x ]] possuir ?

Como o valor de uma variável é dado pelo seu contexto circun-


dante, não podemos dizer o seu valor isoladamente.

Podemos solucionar este problema, dando a Eval[[ ]] um parâme-


tro extra ρ, o qual representa a informação contextual de uma
variável. O argumento ρ é chamado de ambiente (environment) e
é uma função que mapeia nomes de variáveis para seus valores.

Assim: Eval[[ x ]] ρ = ρ x

A notação (ρ x), no lado direito significa “a função ρ aplicada ao


argumento x”.

Para tratar aplicações, usamos um raciocínio semelhante: É


razoável dizer-se que o o valor de (E1 E2) deveri ser o valor de E1
aplicado ao valor de E2.

Eval[[ E1 E2 ]] ρ = (Eval[[ E1 ]] ρ) (Eval[[ E2 ]] ρ)

Prof. Dr. Aldo von Wangenheim


INE 5363 - Programação Funcional - Transparência 54

O caso final é o de uma abstração lambda Eval[[ λx.E ]] ρ.


Certamente será uma função, de forma que nós o podemos definir
completamente dando o seu valor quando aplicada a um argu-
mento arbitrário a:

( Eval[[ λx.E ]] ρ) a

Resumindo:
• O valor de uma abstração lambda, aplicada a um argumento, é o
valor do corpo da abstração, em um contexto onde o parâmetro
formal está atado ao argumento.
• Formalmente: Eval[[ λx.E ]] ρ a = Eval[[ E ]] ρ [x=a]
onde a notação x=a significa “a função ρ extendida com a infor-
mação de que a variável x está atada ao valor a”.
• De forma mais precisa:
ρ [x=a]x = a
ρ [x=a]y = ρ y

caso y seja uma variável diferente de x.

Prof. Dr. Aldo von Wangenheim


INE 5363 - Programação Funcional - Transparência 55

A parte de constantes e funções embutidas, as quais necessitam


de um tratamento individual que veremos na seção 2.5.3, foi pro-
vida nesta seção uma semântica denotacional simples para o cál-
culo lambda.

Resumo geral:
Eval[[ k ]] ρ = vide seção 2.5.3
Eval[[ x ]] ρ =ρx
Eval[[ E1 E2 ]] ρ = (Eval[[ E1 ]] ρ) (Eval[[ E2 ]] ρ)
Eval[[ λx.E ]] ρ a = Eval[[ E ]] ρ [x=a]
onde:
k é uma constante ou função embutida,
x é uma variável,
E, E1, E2 são expressões-lambda.

Nota sobre a notação:

Como vimos, o ambiente r é um argumento essencial para Eval.


Em todas a sutilizações de Eval, ρ tem um papel crucial e é sem-
pre necessário. Por um aquestão de simplicidade, costuma-se
omitir o ambiente ρ ao escrever Eval.

Portanto:

Eval[[ E1 ]] ρ = Eval[[ E2 ]] ρ

significa a mesma coisa que:

Eval[[ E1 ]] = Eval[[ E2 ]]

Prof. Dr. Aldo von Wangenheim


INE 5363 - Programação Funcional - Transparência 56

2.5.2. O Símbolo 8

Uma das características mais úteis da teoria descrita nesta seção,


é que ela nos permite raciocinar a respeito da terminação ou não
terminação de programas.

• Descrição da semântica de expressões que não atingem a


forma normal: Como foi descrito antes, a redução de uma
expressão pode não atingir uma forma normal.

• Para descrever o valor de uma expressão dessas, incluímos o


elemento 8, pronunciado “fundo”, no domínio dos valores de
expressões, o qual é o valor de uma expressão sem uma
forma normal:

Eval[[ <expressão sem forma normal>]] = 8

• 8 possui um significado matemático perfeitamente respeitável na


Teoria dos Domínios.
• À semelhança do símbolo 0 (que também está para “nada”), seu
uso muitas vezes nos permite escrever equações sucintas ao
invés de montes de palavras obtusas.
• Ao invés de dizermos “a evaluação da expressao E falha em ter-
minar”, dizemos: Eval[[ E ]] = 8

Prof. Dr. Aldo von Wangenheim


INE 5363 - Programação Funcional - Transparência 57

2.5.3. Definindo a Semântica de Funções Embutidas e Constantes

Nesta seção veremos como definir o valor de Eval[[ k ]] onde k é


uma constante ou função embutida.

Exemplo: Qual é o valor de Eval[[ * ]] ?

Certamente é uma função de dois argumentos e nós podemos


definí-la dando o valor desta função quando aplicada a argumen-
tos arbitrários:
Eval[[ ∗ ]] a b = a x b

que dá o valor do cálculo lambda * em termos da operação


matemática de multiplicação x.

A distinção entre ∗ e x é crucial:


• ∗ é uma construção sintática do cálculo lambda.
• x é uma operação matemática abstrata.

No caso da multiplicação a notação matemática x difere da


notação de programa ∗. No caso da adição, por exemplo, o sím-
bolo + é usado em ambas. É importante notar-se a diferença.

A equação anterior é, porém, uma especificação incompleta para


∗. Temos de definir o que ∗ faz para todo argumento possível,
inclusive 8 . O conjunto completo de equações ficaria então:
Eval[[ ∗ ]] a b = a x b, caso a ≠ 8 e b ≠ 8
Eval[[ ∗ ]] 8 b = 8
Eval[[ ∗ ]] a 8 = 8

Prof. Dr. Aldo von Wangenheim


INE 5363 - Programação Funcional - Transparência 58

As duas novas equações complementam a definição de ∗, especi-


ficando que, se algum dos argumentos de ∗ falha em terminar,
então da mesma forma o fará a aplicação de ∗.

Uma outra forma, mais “inteligente” de se definir a multiplicação


seria através de um operador de multiplicação #:
Eval[[ # ]] a b = a x b, caso a ≠ 8 e b ≠ 8 e a ≠
Eval[[ # ]] b=
Eval[[ # ]] 8 b = 8
Eval[[ # ]] a 8 = 8

• Estas equações implicam que # deveria primeira avaliar o seu


primeiro argumento e, caso este retorne 0, retornar o valor zero,
sem examinar o segundo argumento de todo.
• O uso de # ao invés de ∗ poderia levar à avaliação de algumas
expressões que de outra forma não terminariam.

O ponto a ser observado nos exemplos acima é o de que o uso de


equações “semânticas” para funções embutidas nos permite
expressar variações sutis no seu comportamento, o que é muito
difícil de se realizar somente através de regras de redução.

 As equações semânticas para uma função ao mesmo tempo


especificam o significado da função e implicam seu compor-
tamento operacional (regras de redução).

Prof. Dr. Aldo von Wangenheim


INE 5363 - Programação Funcional - Transparência 59

2.5.4. Estriticidade

Dizemos que uma função é estrita se temos certeza de que vamos


necessitar do valor de seu argumento.
• Caso uma função f com certeza necessite do valor de seu argu-
mento e a avaliação do argumento não termina, então a aplica-
ção de f ao argumento vai com certeza não terminar.

Esta descrição leva a uma definição concisa de estriticidade:

Uma função f é estrita se e somente se f 8 = 8

Esta definição pode ser generalizada da mesma forma para fun-


ções de muitos argumentos.

Por exemplo: se g é uma função de três argumentos, então g é


estrita em seu segundo argumento se e somente se:

g aVc=V

2.5.5. Fim:

Nestas aulas foi dada uma visão, apenas superficial, do cálculo


lambda. O objetivo foi só mostrar o “que está por trás” da imple-
mentação de liguagens funcionais, já que não se pretende nesta
disciplina ensinar o uso do cálculo lambda para a criação de novas
linguagens funcionais.

2.5.6. LISP:
• Início próxima aula. Uma sugestão para leitura é:
Oakey, Steve; LISP para Micros. Editora Campus, 1986
ISBN 85-7001-326-4

Prof. Dr. Aldo von Wangenheim

Você também pode gostar