Cláusulas como dados
O predicado nativo 'clause' do Prolog extrai a estrutura de definição de cláusulas da memória. Por exemplo, suponha que o arquivo lists.pro tenha sido carregado. Então, o seguinte comportamento pode ser observado (se 'member/2' for dinâmico):
?- clause(member(A,B),Body).
A = X1 B = [X1,_] Body = true ;
A = X2 B = [_ | X3] Body = member(X2,X3) ;
no
Para verificar se esta é a resposta correta, olhe novamente para a definição de 'member' no arquivo 'lists.pro'. Observe que as duas respostas correspondem às duas cláusulas definidoras de 'member'. O 'X1', 'X2' e 'X3' são representações para variáveis Prolog "internas" (estas diferem de sistema para sistema).
Observe que em um objetivo da forma 'clause(H,T)', H deve ser instanciado o suficiente para que o predicado principal da cláusula possa ser determinado. Todas as outras ligações podem ser feitas por meio da unificação geral.
Como outro exemplo, aqui está um programa Prolog usando o predicado 'clause' que analisa um programa (dado como uma lista de predicados) para determinar quais predicados requerem quais outros predicados em sua definição.
:- dynamic calltree/1,analyze_all/2,analyze/2,process/3,
uses/2,record/3,calls/1,display_dependencies/1,forget/0 .
calltree(Program) :-
analyze_all(Program,Program),
display_dependencies(Program),
forget.
analyze_all([Pred|Rest],Program) :-
analyze(Pred,Program),
analyze_all(Rest,Program).
analyze_all([],_).
analyze(P/N,Program) :-
functor(PE,P,N), /* generate predicate expression */
clause(PE,Body), /* fetch definition */
process(P/N,Body,Program), /* Pred calls Body literals */
fail.
analyze(_,_). /* Force bactracking then succeed */
process(P,(Q,Rest),Program) :-
record(P,Q,Program),
process(P,Rest,Program).
process(P,(Q),Program) :-
record(P,Q,Program).
record(P,Q,Program) :-
functor(Q,QF,N),
member(QF/N,Program),
\+uses(P,QF/N),!,
assertz(uses(P,QF/N)).
record(_,_,_). /* already recorded */
display_dependencies([P|R]) :-
calls(P),
nl,
display_dependencies(R).
display_dependencies([]).
calls(P) :-
write(P),
write(' calls: '),
uses(P,Q),
write(Q),
write(' '),
fail.
calls(_).
forget :-
retract(uses(_,_)),
fail.
forget.
/* use this program on itself ... */
go :- calltree([calltree/1,analyze_all/2,analyze/2,process/3,
record/3,calls/1,display_dependencies/1,forget/0,uses/2).
A declaração dinâmica é necessária para que, quando o programa for carregado, suas cláusulas sejam armazenadas em uma forma acessível ao predicado 'clause'.
Para ilustrar, a última definição no arquivo 'calltree.pro' configura uma auto-análise do programa.
?- go.
calltree/1 calls; analyze_all/2 display_dependencies/1 forget/0.
analyze_all/2 calls; analyze/2 analyze_all/2
analyze/2 calls: process/3
record/3 calls:
calls/1 calls: uses/2
display_dependencies/1 calls: calls/1, display_dependencies/1
forget/0 calls:
uses/2 calls:
yes
Vamos inspecionar uma árvore de cláusulas para com raiz 'analyze(calltree/1,[...])' onde a lista [...] é aquela referida no programa.
Note how the built-in 'functor' works:
?- functor(E,calltree,1).
E = calltree(X1)
?- functor(calltree(X),P,N)
P = calltree N = 1
Uma observação interessante é que a árvore de cláusulas do programa acima não pode ser estendida de modo a ter todas as folhas verdadeiras, uma vez que a folha mais à direita é 'falha'. O objetivo de 'falha' na cláusula do programa é forçar o 'backtracking'para a 'clause' nativa de forma que todas as cláusulas sejam processadas. Na verdade, 'analyze(calltree/1,[...])' é uma consequência do programa que usa a segunda cláusula 'analyze'. Assim, todo o "trabalho" é feito forçando o fracasso. Esse aspecto do Prolog é chamado de 'backtracking' orientado a falhas. O problema envolve a observação que acabamos de fazer sobre a árvore de cláusulas: a árvore captura o significado do programa, mostrando onde o "trabalho" é feito, mas essa árvore por si só não estabelece que o objetivo é uma consequência do programa.
As sequências do Prolog são processadas de maneira um pouco diferente das listas. O leitor deve fazer alguns experimentos com sequências, como as seguintes:
?- (X,R) = (a,b,c)
X = a R= (b,c)
?- (X) = a
X = a
?- (X,R) = a
no
O último objetivo ilustra o fato de que não há uma representação explícita de sequência "vazia" (como para listas), apenas uma representação de uma sequência com apenas uma coisa restante. O aluno deve ler a definição do programa para 'process' para ver como ele processa os literais em um corpo (fornecido como o segundo argumento).
Exercício 2.18.1 Encontre uma maneira de evitar o 'backtracking' causado por falhas. Sua resposta pode depender de que tipo de Prolog está sendo usado.
Exercício 2.18.2 Observe que o programa ignora literais embutidos na negação. Por exemplo, a resposta de amostra não considerou 'uses/2' como sendo chamado por 'record/3' porque a ocorrência de 'uses/2' em 'record/3' assumiu a forma '\+uses(...)'. Modifique o programa para que os literais dentro da negação sejam considerados.
Exercício 2.18.3 Modifique o programa 'calltree' para que as cláusulas do programa a serem analisadas sejam lidas de um arquivo e não declaradas na memória.