O cálculo-λ lida apenas com um tipo de dado: funções. Porém, com funções, o tipo numérico mais simples de ser representado, portanto o mais importante, são os números naturais.
A representação de números naturais pode parecer estranha à primeira vista, mas se torna muito simples depois de entendida:
0 ≡ λsz.z
1 ≡ λsz.sz
2 ≡ λsz.s(sz)
3 ≡ λsz.s(s(sz))
4 ≡ λsz.s(s(s(sz)))
A lógica é a seguinte: são funções que recebem dois argumentos. O primeiro (s
) deve ser uma função de incremento – cuja resposta é chamada
“sucessor”, enquanto o segundo uma função que represente o zero
(z
).
Para o zero, o sucessor não é aplicado (z
); para o um é aplicado apenas uma vez (sz
, sucessor de zero); para o dois são feitos dois
passos de sucessão (s(sz)
, sucessor do sucessor de zero); para o
três, três passos (s(s(sz))
); e assim por diante.
Algumas linguagens de programação já incorporaram esse conceito para a representação de números. Por exemplo, Idris define números naturais da seguinte forma (suprimindo docstrings):
data Nat : Type where
Z : Nat
S : Nat -> Nat
Então Z
é zero, S Z
é um, S (S Z)
é 2, S (S (S Z))
é três, e assim por diante.
Podemos emular esse comportamento em Prolog.
Primeiro precisamos de um predicado que informe o que são números naturais segundo o cálculo-λ. Podemos usar nat/1
, seguindo a referência de
Idris.
Precisamos informar que z
(zero) é um número natural, e que o sucessor de um número natural também é um número natural:
nat(z).
nat(s(N)) :- nat(N).
Resolvido!
Precisamos agora de um predicado que faça a conversão para números inteiros.
A forma mais simples seria:
to_int(z, 0).
to_int(s(N), R) :- to_int(N, R1), R is R1 + 1.
Estaria resolvido, não fosse um problema: esta implementação pode gerar um estouro de pilha, já que cada instância de to_int/2
precisa
aguardar que todas suas filhas de recursão terminem para liberar memória.
A forma de resolver isso é usando um acumulador para aproveitar o recurso de tail-call optimisation.
Então o predicado to_int/2
vai apenas verificar se o valor verificado é natural, e delegar a resolução para o predicado to_int/3
.
Esse outro predicado lida com os seguintes parâmetros: o número natural, oacumulador (que deve ser iniciado como zero) e a resposta.
Daí to_int/2
vira:
to_int(N, R) :- nat(N), to_int(N, 0, R).
Já to_int/3
responde o acumulador quando N
for z
, caso contrário incrementa o acumulador e delega para a próxima instância da
recursão resolver:
to_int(z, A, A).
to_int(s(N), A, R) :- succ(A, A1), to_int(N, A1, R).
Essa construção mágica onde o último parâmetro de um predicado é ligado aopenúltimo do próximo predicado numa cadeia de conjunção lógica possui um
açúcar sintático em Prolog com o operador -->
, que suprimi do
código os dois últimos parâmetros do predicado:
to_int(z, A, A).
to_int(s(N)) --> succ, to_int(N).
Podemos criar também um predicado que converta um número inteiro em natural. Oprincípio é muito similar. Precisamos apenas de um predicado equivalente ao
succ/2
que calcule o sucessor natural:
nsucc(N, s(N)) :- nat(N).
Agora podemos fazer from_int/2
e from_int/3
:
from_int(I, R) :- integer(I), I >= 0, from_int(I, z, R).
from_int(0, A, A).
from_int(I) --> { I1 is I - 1 }, nsucc, from_int(I1).
Já podemos usar nosso natural.pl
:
sh$ swipl -q
:- [natural].
true.
:- natural:to_int(z, X).
X = 0.
:- natural:to_int(s(s(s(z))), X).
X = 3.
:- natural:from_int(8, X).
X = s(s(s(s(s(s(s(s(z)))))))) .
:-
Mas podemos gerar um executável. Primeiro precisamos de um predicado pra servir de meta:
go :- current_prolog_flag(argv, [Argv]),
atom_to_term(Argv, I, []),
from_int(I, N),
writeln(N), !.
go :- current_prolog_flag(os_argv, [_, '-x', Path | _]),
file_base_name(Path, Script),
format('use: ~w <zero_or_positive_integer>~n', [Script]).
Agora precisamos converter o script em executável:
sh$ swipl -q
:- [library(qsave)].
true.
:- [natural].
true.
:- qsave_program(natural, [init_file('natural.pl'), goal(natural:go), toplevel(halt)]).
true.
:-
Finalmente é só executar:
sh$ ./natural 12
s(s(s(s(s(s(s(s(s(s(s(s(z))))))))))))
sh$
Como bônus, seguem dois predicados para calcular se o número natural é par ouímpar:
even(z).
even(s(N)) :- odd(N).
odd(N) :- \+ even(N).
Observação: \+
é negação lógica em Prolog; a parada tanto de even/1
quanto de odd/1
é even(z)
, que informa que
zero é par.
Código para baixar: natural.pl.
Concept | Functional | Logical