Cláusulas como dados

De Augusto Baffa Wiki
Ir para navegação Ir para pesquisar

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.

Fig. 2.18

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.


Veja Também