Playlists
Computabilidade é uma área de estudo central da Ciência da Computação. Ela estuda a possibilidade de resolver um problema seguindo um algoritmo.
Wikipedia - Domínio Público
Cálculo λ
Sistema formal para expressar computação baseado em abstração de funções e aplicação usando apenas atribuições de nomes e substituições .
Criado por Alonzo Church na década de 1930

Figure 1: On Computable Numbers, With An Application To the Entscheidungsproblem. A. M. Turing

Figure 2: On Computable Numbers, With An Application To the Entscheidungsproblem. A. M. Turing

Figure 3: On Computable Numbers, With An Application To the Entscheidungsproblem. A. M. Turing
Problemas associados:
Nas linguagens que vocês aprenderam até agora, temos:
x = x + 1)Mas do que realmente precisamos para programar?
Cálculo \(\lambda\) Descreve computação apenas utilizando… funções !!
x = x + 1)Uma linguagem deve ser descrita em função de sua sintaxe e semântica (vocês estudarão isso em compiladores), ou de maneira menos formal, como você escreve e o que significa.
“Whatever the next 700 languages turn out to be, they will surely be variants of lambda calculus.”
Peter Landin, 1966
“The next 700 programming languages”. Communications of the ACM. Association for Computing Machinery. 9 (3): 157–166. https://doi.org/10.1145%2F365230.365257
\->e ::= x
| \x -> e
| e1 e2
Um programa é definido por uma expressão e, ou
termos -\(\lambda\) que podem assumir uma de três formas:
x, y, z, um nome que assumirá um valor
durante a computação.\x -> e,x é o parâmetro formal, e é o corpo da funçãox compute ee1 e2, aplique o argumento e2 na função e1 (e1(e2)).e_i é uma expressão!Qual dos seguintes termos está sintaticamente incorreto?
(a) \(\x -> x) -> y
(b) \x -> x x
(c) \x -> x (y x)
(d) Alternativas a e c
(e) Todas acima
\x -> x -- função identidade
\x -> (\y -> y) -- retorna a função identidade
\f -> f (\x -> x) -- função que aplica seu argumento
-- (que é uma função)
-- na função identidade
Se desejarmos escrever funções que recebem mais de um argumento podemos escrever:
\x -> (\y -> y) -- recebe dois args e retorna o segundo
\x -> (\y -> x) -- recebe dois args e retorna o primeiro
Contudo essa sintaxe pode se tornar inconveniente muito rapidamente. Por isto, utilizaremos a seguinte notação simplificada (açúcares sintáticos) para simplificar a redação dessas expressões:
| original | syntatic sugar |
|---|---|
(((e1 e2) e3) e4) |
e1 e2 e3 e4 |
\x -> (\y -> (\z -> e)) |
\x -> \y -> \z -> e |
\x -> \y -> \z -> e |
\x y z -> e |
Escopo indica a visibilidade de uma variável. Em C, Java:
int x; /* x está visível aqui, mas y não */
{
int y; /* x e y estão visíveis */
}
/* y deixou de existir :( */
\x -> e
x é uma variávele é o escopo de xx em e está ligada
( bound ) por \x:\x -> x
\x -> \y -> x
Por outro lado x está livre ( free ) se não
está dentro de uma abstração:
x y -- não tem \
\y -> x y -- x vem de outro lugar
(\x -> \y -> x) x -- o segundo x é diferente do primeiro
Na expressão abaixo, x é ligado ou livre?
(\x -> x) x
e não tem variáveis livres, então e é uma expressão fechada .Qual é a menor expressão fechada possível?
\x -> x
Adaptado de [SK].
As funções lambda são anônimas, então elas SEMPRE são denotadas
pela sua própria expressão escrita de forma completa. Contudo,
como convenção de notação (e por conveniência) nesse texto
utilizamos sinônimos escritos com todas as letras em maiúsculas
para expressões que são utilizadas repetidamente. Exemplo:
ZERO, INC, SOMA, Y.
Sempre escritos em letras maiúsculas, os sinônimos como por
exemplo \(\text{TESTE} = e_1e_2\) devem ser entendidos como uma expressão
completa e fechada. Assim, \(e_0\text{TESTE}\) é o mesmo que \(e_0~(\text{TESTE})\) que
é o mesmo que \(e_o~(e_1~e_2)\).
Aplicação de funções é associativa à esquerda. Isso significa que \(e_1e_2e_3\) equivale a \(((e_1~e_2)~e_3)\).
O escopo de uma variável \(x\), como em \(\lambda x\) sempre se estende o máximo possível para a direita. Logo:
\(\lambda x~.e_1e_2e_3\) é equivalente a \((\lambda x.(e_1e_2e_3))\) e NÃO a \(((\lambda x.e_1e_2)~e_3)\).
Isso significa que a aplicação de funções tem maior precedência
do que definição de funções e que parênteses são necessários
para escrever \((\lambda x~.e_1e_2)e_3\), nos casos que \(e_3\) deve ser usado
como um argumento para a função \(\lambda x.e_1e_2\) e não parte desta
função propriamente dita.
\(\lambda xyz~.e\) é um açúcar sintático para \((\lambda x.(\lambda y.(\lambda z.~e)))\)
Exemplo:
Como a sintaxe do cálculo lambda é realmente minimalista, às vezes pode ser complicado definir exatamente como uma expressão deve ser interpretada. O exemplo abaixo, retirado de [SK], ilustra como colocar os parêntes em uma expressão lambda de modo a evidenciar a organização dos termos na seguinte expressão:
\((\lambda n~.\lambda f.\lambda x.f(nfx))(\lambda g.\lambda y.gy)\)
Utilizando a regra 4, primeiramente identificamos as abstrações. Observe que as abstrações do primeiro termo são limitadas pelo conjunto de parênteses:
\((\lambda x~.f(nfx)) \quad (\lambda y~.gy)\)
\((\lambda f~.(\lambda x.f(nfx))) \quad (\lambda g~.(\lambda y.gy))\)
\((\lambda n~.(\lambda f.(\lambda x.f(nfx))))\)
Em seguida, agrupamos as combinações pela associatividade à esquerda (Regra 3) e chegamos à expressão final:
\(((\lambda n~.(\lambda f.(\lambda x.(f((nf).x)))))(\lambda g(\lambda y.(gy))))\)
Para simplificar ou avaliar uma expressão lambda, efetuamos uma sequência de reduções até que nenhuma redução possa ser mais aplicada. A regra principal para fazer a avaliação/redução de uma expressão lambda é a redução β , que representa a aplicação da função.
Contudo, como a substituição de variáveis livres em uma expressão pode causar capturas, é necessário também que tenhamos um mecanismo para renomear uma variável ligada. A tal operação damos o nome de redução α .
Redução α Sejam v e w variáveis e e uma expressão
lambda. Então uma redução α é definida como:
\v -> e α=> \w -> e[v := w]
e[v := w] significa: “e com todas as ocorrências livres
de v substituídas por w”.Ou de maneira mais simplificada: Reduções α renomeiam as variáveis de uma função para evitar conflitos.
-- Exemplo 1
\x -> x
α=> \y -> y
-- Exemplo 2
\y -> (\f -> f x) y
α=> \z -> (\f -> f x) z
α=> \z -> (\g -> g x) z
-- Exemplo 3
\x -> (\x -> x) x
α=> \y -> (\x -> x) y
Note que no exemplo 3 acima, apenas as ocorrências livres de x no
corpo da função foram substituídas.
Redução β Seja x uma variável, e e1 e e2 expressões
lambdas. Então uma redução β é definida como:
(\x -> e1) e2 => e1[x := e2]
e1[x := e2] significa, “e1 com todas as ocorrências livres
de x substituídas por e2.”(\x -> e1) e2, β-reduz para e1[x := e2](\x -> x) 2 => 2
(\f -> f (\x -> x)) (somar 1) => (somar 1) (\x -> x)
(\x -> (\y -> y)) 3 => ???
Um termo-\(\lambda\) na forma (\x -> e1) e2 é chamado de expressão redutível ( reducible expression ) ou redex
O termo está em sua forma normal se não contém nenhum redex .
Quais dos termos abaixo não está na forma normal?
x
x y
(\x -> x) y
x (\y -> y)
e é avaliado para e' se existe uma
sequência de passos:e => e1 => ... => en => e'
e' seja uma forma normal.-- Exemplo 1
(\x -> x) 3 => 3
--Exemplo 2
(\f -> f (\x -> x)) (\x -> x)
=> (\x -> x) (\x -> x)
=> (\x -> x)
Além das reduções \(\alpha\) e \(\beta\) que são as principais, existem algumas outras (e.g., \(\delta\)) que não entraremos em detalhes neste texto. Abaixo descrevemos brevemente a redução \(\eta\) que para os nossos propósitos de simplificação de expressões pode ser muito útil.
v uma variável, e e uma expressão
lambda (denotando uma função). Se v não tem ocorrências livres em
e, então: \v -> e v
η=> e
Leitura: η=> → “eta-reduz para”.
Esta redução é, na verdade, trivial (e fácil de se enxergar) quando pensamos em funções comuns. Por exemplo: \(f(x, y) = g (x, y), \forall x, y \implies f = g\).
(\x -> x x) (\x -> x x)
β=> (\x -> x x) (\x -> x x)
OMEGA = (\x -> x x) (\x -> x x)
(\x -> \y -> y) OMEGA
Essa expressão pode ser reduzida pra forma normal? Tente em casa. 😀
Vamos ver como representar o resto dessas funcionalidades com cálculo λ! Comecemos com Booleanos.
Antes, é razoável nos perguntarmos:
Para que utilizamos Verdadeiro e Falso?
if b then e1 else e2.Nós já implementamos as funções necessárias para essa definição em outros slides 😄
Verdadeiro = \x y -> x
Falso = \x y -> y
IF = \b x y -> b x y
IF Verdadeiro 2 3
=> (\b x y -> b x y) Verdadeiro 2 3
=> (\x y -> Verdadeiro x y) 2 3
=> (\y -> Verdadeiro 2 y) 3
=> Verdadeiro 2 3
=> (\x y -> x) 2 3
=> (\y -> 2) 3
=> 2
Defina as seguintes funções:
NOT = \b -> ???
AND = \b1 b2 -> ???
OR = \b1 b2 -> ???
Ainda dá pra melhorar?
Linguagens reais tem uma pancada de funcionalidades:
Vamos agora criar registros.
Vamos começar com registros com dois elementos (ou, pares )
Para que e como utilizamos pares?
A API de Pares
Precisamos definir três funções:
PAIR = \x y -> ??? -- Cria um par com elementos x e y
-- { fst : x, snd : y }
FST = \p -> ??? -- Devolve o primeiro elemento
-- p.fst
SND = \p -> ??? -- Devolve o segundo elemento
-- p.snd
Tal que
FST (PAIR abacate banana)
=> abacate
SND (PAIR abacate banana)
=> banana
x e y é apenas algo que permite você escolher entre
x e y!
x
ou yPAIR = \x y -> (\b -> IF b x y)
FST = \p -> p Verdadeiro -- Chamando com Verdadeiro, pega o primeiro elemento
SND = \p -> p Falso -- Chamando com Falso, pega o segundo elemento
TRIPLE = \x y z -> ???
FST3 = \t -> ???
SND3 = \t -> ???
TRD3 = \t -> ???
Linguagens reais tem uma pancada de funcionalidades:
Vamos agora representar os números.
Considere os números naturais \(0, 1, 2,\ldots\)
Para que (e como) os utilizamos?
inc, dec+, -, *==, <, …ZERO = ???
UM = ???
DOIS = ???
...
N é codificado como a
chamada de uma função N vezes:ZERO = ???
UM = \f x -> f x
DOIS = \f x -> f (f x)
TRES = \f x -> f (f (f x))
...
E o ZERO?
ZERO = ???
UM = \f x -> f x
DOIS = \f x -> f (f x)
TRES = \f x -> f (f (f x))
...
Com que essa definição parece?
ZERO = \f x -> x
UM = \f x -> f x
DOIS = \f x -> f (f x)
TRES = \f x -> f (f (f x))
...
ZERO = \f x -> x
Falso = \x y -> y
Vamos fazer uma função que determina se um número é 0, chamada
isZero. Para isto, vamos começar pela representação de 0. Usando
nossa representação com ADTs:
zero = \f -> \x -> x
Que é a mesma coisa que \(\text{zero} = \lambda f.\lambda x.x\) usando notaçao matemática.
Em outras palavras, zero é uma função constante que devolve a
identidade. Se, então aplicarmos zero a qualquer coisa, vamos
ter como resposta, identidade.
Vamos agora olhar a representação de 1:
um = \ f -> \x -> f x
e de 2:
dois = \ f -> x -> f (f x)
Ou seja, o número n é na verdade quantas vezes aplicamos f
repetidamente em x. O caso base é o 0 que aplica f 0 vez, ou
seja a identidade.
Agora que sabemos disso, o que acontece se aplicarmos zero (que é uma função lambda) em algo?
zero algo
= (\f -> \x -> x) algo
β=> = \x -> x
Vamos analisar uma função mais elaborada agora.
isZero = \n -> (n (\x -> false)) true
Ou em notação matemática:
\(\text{isZero} = \lambda n. (n (\lambda x. \text{false})) \text{true}\)
Vamos ver o que ocorre quando passamos 0 como parâmetro:
isZero zero
= (\n -> (n (\x -> false)) true) zero
β=> = (zero (\x -> false)) true
= ((\f -> \x -> x) (\x -> false)) true
β=> = (\x -> x) true
β=> = true
E se aplicarmos com 1?
isZero um
= (\n -> (n (\x -> false)) true) um
β=> = (um (\x -> false)) true
= ((\ f -> \x -> f x) (\x -> false)) true
β=> = (\x -> (\x -> false) x) true
β=> = (\x -> false) true
β=> = false
Ou seja, a função isZero nada mais é que uma função que identifica
se a função recebida como parâmetro é a função identidade (ou seja
0). Como que ela “identifica” isso?
Aplicando n sobre true e passando como f uma função constante que
devolve false temos que se n for 0, ele é a identidade que
quando aplicada sobre true, vai devolver true, independentemente
de f. Se por outro lado n não for a identidade (e portando
diferente de 0) a aplicação de f vai sempre devolver false (pq é
constante false) identificando, portanto, se o número é 0 ou não.
Outra maneira de ver como isso funciona, é revermos o conceito de
booleanos e a razão pela qual um if desnecessário (você pode usar o
booleano direto como se fosse um if!). Reveja a seção acima sobre
booleanos caso isso ainda não esteja claro para você. Lembre-se também
que definição de false é a mesma que zero! Com isso você consegue
explicar o funcionamento da função isZero?
Um pouco de aritmética com números de Church
INC deve adicionar \(1\) ao número n:INC = \n -> ???
Função INC deve adicionar \(1\) ao número n:
INC = \n -> ???
-- Caso mais simples
INC ZERO = UM
Substituindo pelas definições:
INC = \n -> ???
-- Caso mais simples
INC ZERO = UM
INC (\f x -> x) = \f x -> f x
A operação que deve ser feita para encontrar o novo x é em função do
ZERO:
INC = \n -> ???
-- Caso mais simples
INC ZERO = UM
INC (\f x -> x) = \f x -> f x
INC (\f x -> x) = \f x -> f ((\f x -> x) ???)
Se eu passar como argumento de ZERO o f e o x, obtemos:
INC = \n -> ???
-- Caso mais simples
INC ZERO = UM
INC (\f x -> x) = \f x -> f x
INC (\f x -> x) = \f x -> f ((\f x -> x) ???)
INC (\f x -> x) = \f x -> f ((\f x -> x) f x)
β=> \f x -> f x = UM
-- Mas se você reparar
INC ZERO = \f x -> f (ZERO f x)
-- Assim, podemos fazer
INC = \n -> \f x -> f (n f x)
Vamos testar!
INC = \n -> \f x -> f (n f x)
INC UM -- Substituindo
(\n -> \f x -> f (n f x)) (\f x -> f x)
β=> \f x -> f ((\f x -> f x) f x)
β=> \f x -> f ((\x -> f x) x)
β=> \f x -> f (f x)
= DOIS
INC DOIS -- Substituindo
(\n -> \f x -> f (n f x)) (\f x -> f (f x))
β=> \f x -> f ((\f x -> f (f x)) f x)
β=> \f x -> f ((\x -> f (f x)) x)
β=> \f x -> f (f (f x))
= TRES
Como implementar a função ADD?
ADD = \n m -> ???
ADD DOIS UM = TRES
ADD (\f x -> f (f x)) (\f x -> f x) = (\f x -> f (f (f x)))
f s das parcelasf a um número já existente: INCO pulo do gato!
DOIS INC -- Substituindo
= (\f x -> f (f x)) INC
β=> (\x -> INC (INC x)) = SOMA_DOIS
Que é uma função que soma 2 a qualquer número x!
Será que isso funciona para qualquer caso?
QUATRO INC -- Substituindo
= (\f x -> f (f (f (f x))) INC
β=> (\x -> INC (INC (INC (INC x))) = SOMA_QUATRO
Dado qualquer número x incrementa x, 4 vezes.
Agora estamos prontos para fazer a função ADD!
Como implementar a função ADD?
E finalmente vamos terminar vendo como fazer chamadas recursivas.
Wikipedia/Gleb.svechnikov CC BY-SA 4.0
É tão importante que alguns chegam até a tatuá-lo!

Um vídeo um pouco mais “sério”:

Figure 5: Essentials: Functional Programming’s Y Combinator - Computerphile - https://www.youtube.com/watch?v=9T8A89jgeTI
Em Haskell, poderíamos escrevê-la da seguinte maneira:
fat x = if x == 0 then 1 else x * fat (x - 1)
-- Ou usando lambda
fat = \x -> if x == 0 then 1 else x * fat (x - 1)
fat faz referência a si própria por nome!Podemos contornar o problema criando uma nova versão de fat que
não referencie nomes não definidos pela própria expressão
fat = \f -> \x -> if x == 0 then 1 else x * f (x - 1)
-- Ou usando açúcar sintático
fat = \f x -> if x == 0 then 1 else x * f (x - 1)
Pergunta
Quero calcular fatorial de 3. O que passar como f na expressão abaixo?
fat ??? 3
fat = \f x -> if x == 0 then 1 else x * f (x - 1)
O mais conveniente seria usar um valor para f, digamos p, tal
que
p ≡ fat p, ou em outras palavras, p n ≡ fat p n
p 3 ≡ fat p 3 ≡ \(6\)p seja ponto fixo 1 da
função fat.Considere a expressão
A = (\x -> f (x x)) (\x -> f (x x))
(\x -> x x) (\x -> x x)) não é mera coincidênciaLogo:
A = (\x -> f (x x)) (\x -> f (x x))
= f ((\x -> f (x x)) (\x -> f (x x))) -- por redução β
= f A
Ou seja, como A = f A para todo f , então A é ponto fixo de f!
Podemos, então, finalmente construir o combinador Y ( Y-combinator )
O combinador Y nada mais é do que uma expressão \(\lambda\) que devolve um ponto fixo para qualquer função \(\lambda\)
A = (\x -> f (x x)) (\x -> f (x x)) -- Definida acima
Y = \f -> A -- liga a variável f em A
Y = \f -> (\x -> f (x x)) (\x -> f (x x)) -- Combinador Y
Ou, em notação matemática:
\(\lambda f.\,(\lambda x.\, f\enspace (x\enspace x)) \enspace(\lambda x.\, f\enspace (x\enspace x))\)
** Como que é? 🤔 **
fA por f A, ou seja, trocamos uma
expressão que contém apenas A por outra que avalia f e que
ainda contém AVamos aplicar o combinador Y ao fatorial.
fat = \f x -> if x == 0 then 1 else x * f (x - 1)
Y = \f -> (\x -> f (x x)) (\x -> f (x x))
-- Quero calcular 3!
Y fat 3
= fat (Y fat) 3 -- Pois (Y fat) é ponto fixo de fat!
= (\f x -> if x == 0 then 1 else x * f (x - 1)) (Y fat) 3
= (\x -> if x == 0 then 1 else x * (Y fat) (x - 1)) 3
= if 3 == 0 then 1 else 3 * (Y fat) (3 - 1)
= 3 * (Y fat 2)
= 3 * (fat (Y fat) 2)
= 3 * ((\f x -> if x == 0 then 1 else x * f (x - 1)) (Y fat) 2)
= 3 * ((\x -> if x == 0 then 1 else x * (Y fat) (x - 1)) 2)
= 3 * (if 2 == 0 then 1 else 2 * (Y fat) (2 - 1))
= 3 * (2 * (Y fat) 1)
= 3 * (2 * (Y fat) 1)
= 3 * 2 * ((Y fat) 1)
= 6 * (fat (Y fat) 1)
= 6 * ((\f x -> if x == 0 then 1 else x * f (x - 1)) (Y fat) 1)
= 6 * ((\x -> if x == 0 then 1 else x * (Y fat) (x - 1)) 1)
= 6 * (if 1 == 0 then 1 else 1 * (Y fat) (1 - 1))
= 6 * 1 * ((Y fat) 0)
= 6 * (fat (Y fat) 0)
= 6 * ((\f x -> if x == 0 then 1 else x * f (x - 1)) (Y fat) 0)
= 6 * ((\x -> if x == 0 then 1 else x * (Y fat) (x - 1)) 0)
= 6 * (if 0 == 0 then 1 else 0 * (Y fat) (0 - 1))
= 6 * 1
= 6
Checklist completo!
y = \f -> (\x -> f (x x)) (\x -> f (x x))
Se você tentar criar o combinador Y em Haskell usando a expressão acima você receberá o seguinte erro:
• Occurs check: cannot construct the infinite type: t0 ~ t0 -> t
• In the first argument of ‘x’, namely ‘x’
In the first argument of ‘f’, namely ‘(x x)’
In the expression: f (x x)
• Relevant bindings include
x :: t0 -> t (bound at <interactive>:3:29)
f :: t -> t (bound at <interactive>:3:6)
y :: (t -> t) -> t (bound at <interactive>:3:1)
(x x)
x é uma função que recebe um parâmetroa e b os tipos do seu parâmetro e resultado
respectivamente. Logo:x :: a -> b
-- Como na expressão x é aplicada a x, então:
x :: a
-- Logo a = (a -> b) que é um tipo infinito
Haskell não aceita tipos infinitos pois isso poderia causar problemas (laços infinitos) no verificador de tipos do compilador
Há duas soluções possíveis
Criar um operador de ponto fixo sem usar lambdas
y :: (a -> a) -> a
y f = f (y f)
Forçar o sistema de tipos do Haskell a engolir os tipos na
marra via unsafeCoerce
import Unsafe.Coerce
y :: (a -> a) -> a
y = \f -> (\x -> f (unsafeCoerce x x)) (\x -> f (unsafeCoerce x x))
Os tipos de y de ambas as soluções são exatamente os mesmos!
Saída do ghci
> fat = \f x -> if x == 0 then 1 else x * f (x - 1)
> import Unsafe.Coerce
> y = \f -> (\x -> f (unsafeCoerce x x)) (\x -> f (unsafeCoerce x x))
> y fat 3
6
> y fat 10
3628800
>
Veja https://rosettacode.org/wiki/Y_combinator para uma lista da implementação do combinador Y em diferentes linguagens de programação.
Adicione parênteses às expressões abaixo para evidenciar a ordem de avaliação:
Reescreva as expressões do Exercício 1 utilizando a sintaxe de Haskell.
Considere a função DUAS_VEZES = \f x -> f (f x) e a função INC
dada. Utilizando-as, defina as funções:
SOMA_QUATRO que devolve o número passado como argumento somado a 4
SOMA_DEZESSEIS que devolve o número passado como argumento somado a 16
SOMA_SESSENTA_E_QUATRO que devolve o número passado como argumento somado a 64
Utilize reduções \(\beta\) (e \(\alpha\) caso ache conveniente) para reduzir as expressões abaixo para a sua forma normal (caso ela exista).
(\ g -> g 5) (\ x -> add x 3)(\x -> (\ y z -> z y) x) p (\x -> x)(\x -> x x x) (\x -> x x x)(\x -> \y -> (add x ((\x -> sub x 3) y))) 5 6(\c -> c (\a -> \b -> b)) ((\a -> \b -> \f -> f a b) p q)DUAS_VEZES (\n -> (mul 2 (add n 1))) 5DUAS_VEZES (DUAS_VEZES (\n -> (mul 2 (add n 1)))) 5DUAS_VEZES DUAS_VEZES sqr 2(\x -> ((\z -> (add x x)) ((\x -> \z -> (z 13 x)) 0 div))) ((\x -> (x 5)) sqr)Utilizandos as funções TRIPLE e TRD3, mostre
passo-a-passo que TRD3 (TRIPLE p q r) = r.
O cálculo \(\lambda\) admite funções com apenas um
único parâmetro formal. Contudo muitas funções necessitam de um ou
mais parâmetros (ex. soma, divisão, …). Para lidar com esses
casos temos duas abordagens possíveis: (i) utilizar pares (ou no
caso geral, tuplas de tamanho apropriado) de elementos como
argumentos; (ii) utilizar a versão curried da função que tem a
propriedade de que seus argumentos são fornecidos um por vez. Por
exemplo, a expressão 3 + 5 poderia ser escrita como ((ADD 3) 5). Considerando as funções PAIR, FST e SND e
as funções abaixo:
CURRY = \f x y -> f (PAIR x y)UNCURRY = \f p -> f (FST p) (SND p)Mostre que:
CURRY (UNCURRY h) = hUNCURRY (CURRY h) (PAIR r s) = h (PAIR r s)Diversas linguagens de programação, em particular as funcionais,
fornecem a construção let para criar e inicializar uma variável
com um valor. Converta as expressões que usam let abaixo para expressões que
usam apenas lambdas.
let x = 5 in let y = (add x 3) in (mul x y)let a = 7 in let g = λx . (mul a x) in let a = 2 in (g 10)Forneça a forma normal das expressões do exercício 7.
Baseando-se na implementação de INC e na definição
dos números de Church, forneça a implementação da operação MULT = \a b -> ... que recebe dois números inteiros a e b e devolve o
valor de a multiplicado por b.
Utilizando o combinador Y, implemente a função recursiva FIB
que dado n devolve o n-ésimo número da sequência de Fibonacci.
Exemplos:
Y FIB ZERO resulta em ZEROY FIB DOIS resulta em UMY FIB SEIS resulta em OITOUtilizando cálculo \(\lambda\) e as funções
desenvolvidas nos exercícios anteriores, implemente uma função
POT que recebe 2 números inteiros, b e p e que devolva o
valor \(b^p\).
Estes slides foram preparados para os cursos de Paradigmas de Programação e Desenvolvimento Orientado a Tipos na UFABC.
Este material pode ser usado livremente desde que sejam mantidos, além deste aviso, os créditos aos autores e instituições.