A recursividade é um dos conceitos mais fundamentais e poderosos da programação — e em Prolog, ela é ainda mais natural, pois a linguagem foi projetada para suportar definições recursivas de forma elegante. O princípio é simples: resolver um problema usando uma instância menor (mais simples) dele mesmo. Para que isso funcione, precisamos de dois elementos essenciais: um caso base (que para a recursão) e um passo recursivo (que reduz o problema e chama a si mesmo).
Exemplo Matemático: Potência (Programa 4.1)
Vamos calcular potência: B^N (base elevada ao expoente). Sabemos que:
- Caso base: B^0 = 1 (qualquer número elevado a 0 é 1).
- Passo recursivo: B^N = B * B^(N-1) (para N > 0).
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
% pot(Base, Expoente, Resultado) % Caso base: qualquer base elevada a 0 é 1 pot(_, 0, 1). % Passo recursivo: B^N = B * B^(N-1) pot(B, N, P) :- N > 0, M is N - 1, pot(B, M, R), P is B * R. |
Estrutura de um Predicado Recursivo
Observe a estrutura:
- Caso base:
pot(_, 0, 1).— a condição mais simples, que não chama a si mesmo. - Passo recursivo:
pot(B, N, P) :- N > 0, M is N-1, pot(B, M, R), P is B * R.— reduz o problema (N diminui), chama recursivamente e depois combina o resultado.
A guarda N > 0 garante que a recursão não seja chamada com expoente negativo,
evitando loops infinitos.
Fluxo de Execução: pot(2, 3, P)
Veja como o Prolog processa a consulta passo a passo:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
[EXPANSÃO] pot(2, 3, P) │ N = 3 > 0, M = 2 │ chama pot(2, 2, R1) │ │ │ ├─ [EXPANSÃO] pot(2, 2, R1) │ │ N = 2 > 0, M = 1 │ │ chama pot(2, 1, R2) │ │ │ │ │ ├─ [EXPANSÃO] pot(2, 1, R2) │ │ │ N = 1 > 0, M = 0 │ │ │ chama pot(2, 0, R3) │ │ │ │ │ │ │ ├─ [BASE] pot(2, 0, 1) SUCESSO │ │ │ │ │ │ │ └─ [RETORNO] R3 = 1 │ │ │ P é 2 * 1 = 2 │ │ │ R2 = 2 │ │ │ │ │ └─ [RETORNO] R2 = 2 │ │ P é 2 * 2 = 4 │ │ R1 = 4 │ │ │ └─ [RETORNO] R1 = 4 │ P é 2 * 4 = 8 │ └─ SUCESSO: P = 8 |
Interpretação:
- O Prolog expande as chamadas recursivas até atingir o caso base.
- Depois, retorna os resultados de baixo para cima, combinando os valores.
- No final,
Pé unificado com8(que é 2^3).
Exemplo do Fatorial (Programa 4.2)
O fatorial é outro exemplo clássico:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
% fat(N, F) → F = N! % Caso base: 0! = 1 fat(0, 1). % Passo recursivo: N! = N * (N-1)! fat(N, F) :- N > 0, M is N - 1, fat(M, R), F is N * R. |
A estrutura é idêntica à da potência: um caso base (0! = 1) e um passo recursivo que reduz N até chegar a 0.
Fluxo para fat(3, F):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
fat(3, F) │ N=3 > 0, M=2 │ chama fat(2, R1) │ │ │ ├─ fat(2, R1) │ │ N=2 > 0, M=1 │ │ chama fat(1, R2) │ │ │ │ │ ├─ fat(1, R2) │ │ │ N=1 > 0, M=0 │ │ │ chama fat(0, R3) │ │ │ │ │ │ │ ├─ [BASE] fat(0, 1) SUCESSO │ │ │ │ │ │ │ └─ [RETORNO] R3 = 1 │ │ │ R2 = 1 * 1 = 1 │ │ │ │ │ └─ [RETORNO] R2 = 1 │ │ R1 = 2 * 1 = 2 │ │ │ └─ [RETORNO] R1 = 2 │ F = 3 * 2 = 6 │ └─ SUCESSO: F = 6 |
A Hipótese da Recursividade
Ao escrever um predicado recursivo, você faz uma suposição fundamental:
assumir que a chamada recursiva funciona corretamente para uma instância menor do problema.
Essa é a hipótese da recursividade. No caso da potência, assumimos que
pot(B, M, R) realmente calcula B^M corretamente, e então usamos esse resultado
para calcular B^N. Essa confiança na chamada recursiva é o que torna a recursão poderosa
e elegante.
Importante: A condição de parada (caso base) é obrigatória para
evitar recursão infinita. Sem ela, o Prolog entraria em um loop até estourar a pilha de chamadas.
A guarda N > 0 no passo recursivo garante que nunca chamaremos pot com
um expoente negativo.
Exercícios Propostos
-
Fibonacci: Implemente
fib(N, F)que calcula o N-ésimo termo da sequência de Fibonacci (F(0) = 0, F(1) = 1, F(N) = F(N-1) + F(N-2)). Use dois casos base. -
Soma de lista: Implemente
soma_lista([], 0).esoma_lista([H|T], S) :- soma_lista(T, R), S is H + R. -
Inverter lista: Crie
inverter([], []).einverter([H|T], Inv) :- inverter(T, R), append(R, [H], Inv). -
Pertencimento: Implemente
membro(X, [X|_]).emembro(X, [_|T]) :- membro(X, T). -
Desafio: Implemente
max_lista([X], X).emax_lista([H|T], M) :- max_lista(T, R), M is max(H, R).
Conclusão
A recursividade é o coração da programação lógica. Em Prolog, ela é expressa de forma natural através de regras que se referem a si mesmas, com um caso base que encerra a cadeia e um passo recursivo que reduz o problema. Esse padrão aparece em cálculos matemáticos (potência, fatorial), manipulação de listas, definição de estruturas de dados e muito mais. Dominar a recursividade é dominar o pensamento declarativo e estrutural que o Prolog exige. Pratique com os exercícios acima e explore a beleza da auto-referência lógica!