Prolog/Recursão

Origem: Wikilivros, livros abertos por um mundo aberto.

Regras recursivas devem ser permitidas a fim de tornar a linguagem útil para muitas aplicações. Um predicado definido por uma regra recursiva deve necessariamente ter, no mínimo uma definição não recursiva. Se isto não acontecer, a definição é logicamente mal-formada e o programa ficaria em laço infinito.

Motivação[editar | editar código-fonte]

Considerando o seguinte programa:

 pai(david, john).
 pai(jim, david).
 pai(steve, jim).
 pai(nathan, steve).

 avo(A, B) :- pai(A, X), pai(X, B).

seria interessante poder definir ancestral(X, Y). Uma forma não-recursiva seria impraticável, pois seria necessário incluir todas as possíveis gerações:

 ancestor(A,B) :- pai(A, B).
 ancestor(A,B) :- pai(A, X), pai(X, B).
 ancestor(A,B) :- pai(A, X), pai(X, Y), pai(Y, B).
 ancestor(A,B) :- pai(A, X), pai(X, Y), pai(Y, Z), pai(Z,B).
% continue escrevendo - e ainda falta incluir as mães!

Claramente, este programa não é elegante.

Recursão - primeiro exemplo[editar | editar código-fonte]

A implementação da regra sobre ancestralidade usando recursão é muito mais simples e elegante:

 ancestral(X,Y) :- mae(X,Y).
 ancestral(X,Y) :- pai(X,Y).
 ancestral(X,Y) :- mae(X,Z), ancestral(Z,Y).
 ancestral(X,Y) :- pai(X,Z), ancestral(Z,Y).

Deve-se tomar cuidado com a ordem na qual unificações são procurados para objetivos. Se invertermos a ordem nas regras recursivas do predicado ancestral, isto é

 ancestral(X,Y) :- ancestral(Z,Y), mae(X,Z).
 ancestral(X,Y) :- ancestral(Z,Y), pai(X,Z).

a consulta resultará uma recursão infinita.


Recursão em cauda[editar | editar código-fonte]

Uma função pode ser definida por recursão em cauda da seguinte maneira:


f(x): se c(x) então g(x) senão f(h(x))

onde: - é algum tipo de condição sobre o valor de ; - são funções definidas com um argumento não definido na função .

A mesma sentença pode ser escrita em Prolog desta forma:

 f(X, Z) :- c(X), g(X, Z).
 f(X, Z) :- h(X, Y), f(Y, Z).

Recursão em cauda é importante e preferível sobre recursão não cauda pois pode ser implementada como uma iteração que é computada usando uma pilha estática.

Recursão não em cauda[editar | editar código-fonte]

Recursão não em cauda pode ser definida pela sentença

f(x): se c(x) então g(x) senão k(x, f(h(x)))

Isto significa que o valor da chamada de recursão é modificado após sua computação. Em Prolog, esta função pode ser implementada assim:

 f(X, Y) :- c(X), g(X, Y).
 f(X, Y) :- h(X, X1), f(X1, Y1), k(X, Y1, Y).

Recursão em não cauda usa um espaço linear na pilha, conseqüentemente evitado caso não seja necessário. Em alguns casos é possível otimizar a implementação. Considere o esquema:

f(n): se n = 0 então a senão k(n, f(n - 1))

Isto pode ser escrito em Prolog usando recursão não em cauda:

 f(0, a).
 f(X, Y) :- X1 is X - 1, f(X1, Y1), k(X, Y1, Y).

(is/2 é a forma de fazer atribuições aritméticas: isto será visto no capítulo Prolog/Matemática). Este código requer espaço linear na pilha para guardar resultados temporários de chamadas recursivas. Usando um acumulador, o código abaixo pode ser escrito usando recursão em cauda:

 f(X, Y) :- f(X, 1, a, Y).
 f(X, M, ACC, ACC) :- M > N.
 f(X, M, ACC, Y) :- M <= N, k(M, ACC, ACC1), M1 is M + 1, f(N, M1, ACC1, Y).

(de novo, estamos usando conceitos matemáticos, no caso os predicados '>'/2 e '<='/2, que serão vistos no capítulo Prolog/Matemática).