A beleza da matemática e do cálculo é que algo não precisa ter uma aplicação prática imediata para ser estudado em profundidade. A aplicabilidade pode surgir 150, 200 anos após sua concepção.
Um dos tópicos mais magníficos do cálculo-λ é o combinador de ponto fixo, apesar de ser de interesse mais acadêmico do que diretamente prático.
Ainda assim, seu entendimento engrandece em tamanha proporção o conhecimento do programador, que vale a pela dedicar-lhe algum tempo.
Cálculo-λ
Cálculo-λ é uma parte da lógica matemática criada da década de 1930 como parte da investigação dos fundamentos da matemática. No cálculo-λ, todos os elementos são funções de primeira classe, inclusive os números.
Por exemplo, o número 2 é uma função que aplica uma outra função duas vezes, e é representado como
O sinal + (ou
A multiplicação é prefixal, não mesoclítica, ou seja, 2x escreve-se como
O cálculo-λ é o princípio de onde deriva a programação funcional.
Ponto fixo
Impossível entender combinador de ponto fixo sem saber o que significa ponto fixo. Ponto fixo é o ponto de uma função onde o valor retornado é igual ao valor fornecido.
Por exemplo, dada a função f(x) = 2x-1, o que em notação cálculo-λ fica
Usando aritmética básica:
Portanto o ponto fixo de
Repare que a recursão sobre o ponto fixo resulta sempre no mesmo resultado. Se:
Podemos substituir
Variáveis livres e vinculadas
Em cálculo-λ, no corpo de uma função, todas as variáveis que vêm de sua assinatura são vinculadas e as que vêm de fora são livres.
Por exemplo, na função:
As variáveis
Combinadores
Combinadores são funções que não possuem variáveis livres. Por exemplo:
A primeira linha é um combinador, já a segunda não.
Veja que, a rigor, números e operadores aritméticos são variáveis livres, porém, como são valores constantes e bem definidos, tratam-se de exceções aceitáveis em um combinador.
Construção let
A construção let é uma construção clássica em cálculo-λ e em linguagens funcionais de programação.
Darei um exemplo: seja
Podemos construir uma função assim, por exemplo, dado o parâmetro
É o mesmo que:
Em Haskell isso pode ser escrito assim:
Em Racket fica assim:
A forma genérica da construção let é:
E sua abertura é:
Função recursiva é aquela que faz chamada a ela mesma, por exemplo, fatorial:
Olhando para o corpo da função, mesmo desconsiderando números e operadores (
A forma de resolver isso é criar uma função que recebe a si mesma na assinatura:
Quando esse combinador recebe como parâmetro o fatorial, ele retorna o próprio fatorial, assim a função fatorial é o ponto fixo dele!
O combinador de ponto fixo é aquele que, ao receber a função como parâmetro, retorna seu ponto fixo – e quando a função recebe o ponto fixo, retorna o próprio ponto fixo, segundo o seguinte comportamento:
Só aqui já seria possível implementar uma função que calcule o ponto fixo. Em Haskell:
E em Lazy Racket:
Porém seria sem sentido, pois essa mesma função que resolve o problema, é parte do problema, já que ela própria possui variável livre.
Resolver este problema não é difícil: basta pegarmos a definição: dada uma função
Data uma função que recebe
De fato isso já funciona em Haskell:
Precisamos de dois parâmetros na primeira posição, portanto dobraremos o
Mais um passo e chegamos ao famoso combinador Y de Curry!
Aplicando o parâmetro
Em Lazy Racket:
O truque é aplicar a função
Por exemplo, o combinador Y em Python:
Esse código entra em loop infinito! Mas se substituirmos
Combinador de Turing:
Combinador de ponto fixo estrito (vimos acima):
Combinador de ponto fixo não padrão:
[]’s
Um dos tópicos mais magníficos do cálculo-λ é o combinador de ponto fixo, apesar de ser de interesse mais acadêmico do que diretamente prático.
Ainda assim, seu entendimento engrandece em tamanha proporção o conhecimento do programador, que vale a pela dedicar-lhe algum tempo.
Conceitos básicos
Há alguns pré-requisitos que você precisa conhecer para começar nosso estudo:Cálculo-λ
Cálculo-λ é uma parte da lógica matemática criada da década de 1930 como parte da investigação dos fundamentos da matemática. No cálculo-λ, todos os elementos são funções de primeira classe, inclusive os números.
Por exemplo, o número 2 é uma função que aplica uma outra função duas vezes, e é representado como
λsz.s(sz)
.O sinal + (ou
S
em alguns textos) significa incremento, e é descrito como λwyx.y(wyx)
. Ou seja, +3 é 3 incrementado, que é igual a 4. 2+3 é a função 2 recebendo como parâmetros + e 3, ou seja, incrementa duas vezes o número 3, +(+3), o que resulta em 5.A multiplicação é prefixal, não mesoclítica, ou seja, 2x escreve-se como
*2x
(ou M2x
) e sua definição é λab.a(b+)0
ou λxyz.x(yz)
.O cálculo-λ é o princípio de onde deriva a programação funcional.
[update 2014-08-25]
A definição de decremento é (caso eu não tenha copiado errado):
[/update]DEC = λnfx.n (λgh.h(gf)) (λu.x) I
Ponto fixo
Impossível entender combinador de ponto fixo sem saber o que significa ponto fixo. Ponto fixo é o ponto de uma função onde o valor retornado é igual ao valor fornecido.
Por exemplo, dada a função f(x) = 2x-1, o que em notação cálculo-λ fica
f = λx.DEC(*2x)
, o ponto fixo é aquele onde fx = x
, ou seja, o resultado é igual ao parâmetro.Usando aritmética básica:
f(x) = 2x - 1 x = 2x - 1 2x - 1 = x 2x - 1 - x = 0 x - 1 = 0 x = 1
Portanto o ponto fixo de
λx.DEC(*2x)
é 1.Repare que a recursão sobre o ponto fixo resulta sempre no mesmo resultado. Se:
x = fx
Podemos substituir
x
por fx
:x = fx = f(fx) = f(f(fx))) = ...
Variáveis livres e vinculadas
Em cálculo-λ, no corpo de uma função, todas as variáveis que vêm de sua assinatura são vinculadas e as que vêm de fora são livres.
Por exemplo, na função:
λfx.Δxf
As variáveis
x
e f
são vinculadas e a variável Δ
é livre.Combinadores
Combinadores são funções que não possuem variáveis livres. Por exemplo:
λx.xx
λx.y
A primeira linha é um combinador, já a segunda não.
Veja que, a rigor, números e operadores aritméticos são variáveis livres, porém, como são valores constantes e bem definidos, tratam-se de exceções aceitáveis em um combinador.
Construção let
A construção let é uma construção clássica em cálculo-λ e em linguagens funcionais de programação.
Darei um exemplo: seja
fx
igual a *2x
em f4
, o resultado é 8 – fx
é o dobro e x
e o caso é f4
. Isso é descrito assim:let fx = *2x in f4
Podemos construir uma função assim, por exemplo, dado o parâmetro
x
, sendo fa
igual a *2a
em fx-1
:λx.let fa = *2a in DEC(fx)
É o mesmo que:
λx.DEC(*2x)
Em Haskell isso pode ser escrito assim:
func x = let f a = 2 * a in f x - 1
Em Racket fica assim:
(describe func
(λ (x)
(let ((f (λ (a) (* 2 a)))
(- (f x) 1))))
A forma genérica da construção let é:
let fx = y in z
E sua abertura é:
(λf.z)(λx.y)
Finalmente: o combinador de ponto fixo
Combinador de ponto fixo é uma função que retorna o ponto fixo da função fornecida como parâmetro. Em cálculo e mesmo em programação isso é muito útil na implementação de funções recursivas.Função recursiva é aquela que faz chamada a ela mesma, por exemplo, fatorial:
FACT = λn.(ISZERO n) 1 (*n (FACT (DEC n)))
Olhando para o corpo da função, mesmo desconsiderando números e operadores (
ISZERO
, 1
, *
e DEC
), ainda há a variável livre FACT
, que não é definida na assinatura da função.[update 2014-08-25]
A definição deISZERO
é:
[/update]TRUE = λxy.x FALSE = λxy.y ISZERO = λn.n(λx.FALSE)TRUE
A forma de resolver isso é criar uma função que recebe a si mesma na assinatura:
ALMOST-FACT = λfn.(ISZERO n) 1 (*n (f (DEC n)))
Quando esse combinador recebe como parâmetro o fatorial, ele retorna o próprio fatorial, assim a função fatorial é o ponto fixo dele!
O combinador de ponto fixo é aquele que, ao receber a função como parâmetro, retorna seu ponto fixo – e quando a função recebe o ponto fixo, retorna o próprio ponto fixo, segundo o seguinte comportamento:
yf = f(yf)
Só aqui já seria possível implementar uma função que calcule o ponto fixo. Em Haskell:
fix f = f (fix f)
E em Lazy Racket:
(define fix
(λ (f)
(f (fix f))))
Porém seria sem sentido, pois essa mesma função que resolve o problema, é parte do problema, já que ela própria possui variável livre.
Resolver este problema não é difícil: basta pegarmos a definição: dada uma função
f
, queremos o ponto x
onde fx
seja igual a x
… é uma construção let!Data uma função que recebe
f
(λf.
), seja x
igual a fx
(let x = fx
) em x
(in x
):λf.let x = fx in x
De fato isso já funciona em Haskell:
fix :: (a -> a) -> a
fix f = let x = f x in x
Combinador Y
Para Scheme (Lazy Racket), precisamos destrinchar um pouco mais, abrindo o let.Precisamos de dois parâmetros na primeira posição, portanto dobraremos o
x
:λf.let x = fx in x
λf.let xx = f(xx) in xx
λf.(λx.xx)(λx.f(xx))
Mais um passo e chegamos ao famoso combinador Y de Curry!
Aplicando o parâmetro
λx.f(xx)
à função λx.xx
, temos (λx.f(xx))(λx.f(xx))
, exatamente o combinador Y de Curry:Y = λf.(λx.f(xx))(λx.f(xx))
Em Lazy Racket:
(define Y
(λ (f)
((λ (x) (f (x x))) (λ (x) (f (x x))))))
Linguagens estritas
A maioria das linguagens não suporta lazy evaluation, então é preciso fazer um truque para que a definição do combinador não entre em loop infinito.O truque é aplicar a função
λsv.sv
(que é o número 1, equivalente à identidade: I = λx.x
). Por exemplo:g = 1g
g = (λsv.sv) g
g = (λv.gv)
Por exemplo, o combinador Y em Python:
Y = lambda f: (lambda x: x(x))(lambda x: f(x(x)))
Esse código entra em loop infinito! Mas se substituirmos
xx
por λv.xxv
, tudo se resolve:Y = lambda f: (lambda x: x(x))(lambda x: f(lambda *args: x(x)(*args)))
Outros combinadores de ponto fixo
Curry foi o que obteve a solução mais simples para o combinador de ponto fixo, mas não foi o único. Há outras soluções possíveis.Combinador de Turing:
Θ = (λx.xx)(λab.b(aab))
Combinador de ponto fixo estrito (vimos acima):
Z = λf.(λx.xx)(λx.f(λv.xxv))
Combinador de ponto fixo não padrão:
B = λwyx.w(yx)
∆ = λx.xx
N = B∆(B(B ∆)B)