3.     Prolog

3.1.   Noções Preliminares

Anterior à introdução do Prolog em si, é necessário que tenhamos noções do que há por trás da concepção da linguagem, ou seja, porque e como ela é como é. Assim, vamos dar uma olhada em duas concepções derivadas da psicologia cognitiva para melhor entendermos para que serviria a linguagem Prolog e como seria sua estruturação.

 ·        A Relação Define O Mundo

Uma primeira concepção pode ser entendida por uma pergunta: o que se relaciona como o quê? Ou: entre que coisas do mundo que conhecemos existe relação? Há relação entre este texto e o que estou pensando? Há relação entre mim e minha mãe? Há relação entre o motorista do ônibus e seus passageiros? Há relação entre dois irmãos? Há relação entre uma caneta que está sobre uma mesa? Há uma relação entre uma calça e uma camisa? E assim poderíamos continuar interrogando-nos sucessivamente até o infinito... E assim entenderíamos que entre todas as coisas que conhecemos existe relação. Obviamente que essa relação pode ser de posição física,de posse, de convívio social, de sentimentos, etc. Praticamente não há limites para uma visão de mundo que busca o relacionamento entre os objetos (físicos ou não) deste mundo.

Basicamente, nada existe na mente humana sem relação.Não podemos conceber a existência de um objeto no mundo que não tenha uma relação no tempo e no espaço.Tudo é relação. A relação entre os objetos é que os define no mundo. Como? Qual a definição de árvore para você? Você consegue definir um vegetal sem concebê-lo no seu meio-ambiente? Este é o fruto da relação:a árvore (vegetal) relaciona-se com a terra, com o ar e com a luz solar. Sem estas relações não existe o objeto árvore.Outro exemplo: dois irmãos existem por ter pais em comum. Sem a relação de paternidade a definição irmãos não existiria.

A importância de considerar-se a relação como definidora dos elementos do mundo é o que nos leva a considerá-la de extrema utilidade na modelagem de representações simbólicas a nível computacional. Isto simplifica a modelagem de elementos que são aparentemente complexos por causa de suas relações,como, por exemplo, a linguagem natural falada pelo homem.

·        A Classificação e A Seriação

Elementos fundamentais para a evolução cognitiva do homem, a classificação e a seriação permitem o agrupamento para discernimento e a organização hierárquica dos elementos do mundo. Para entendermos melhor, vamos analisar o comportamento desde o nascimento: há inicialmente nos bebês movimentos de reflexos, que são repetidos durante meses até o momento em que são combinados entre si e outros elementos sensoriais. Isto dá início ao sistema cíclico de aprendizado. Uma vez portadora deste sistema, a criança pode distinguir os objetos ao seu redor classificando-os e atribuindo a eles relações de hierarquia.

A classificação e a seriação funcionam paralelamente. O motivo deste funcionamento paralelo foi apresentado no parágrafo anterior: ao distinguir um objeto no mundo, ele é definido por sua relação a outros objetos. Inicialmente os bebês não conseguem distinguir entre si mesmos e os objetos do mundo. Eles necessitam da experimentação de suas capacidades sensoriais para, aos poucos, ir formando relações que permitam a constatação da existência de objetos isolados e distintos no mundo. Para nós, um bom exemplo é o da classificação biológica dos seres vivos. No caso do homem, sabemos que é um mamífero primata humano. Com esta afirmação, indicamos que existem mamíferos primatas não-humanos e também mamíferos não-primatas. Ao realizar a distinção entre homens e macacos, faz-se também a associação entre suas formas e funções corporais, o que faz presumir que ambos, embora sendo de uma mesma categoria, têm características distintas. O mesmo ocorre quando comparam-se homens e macacos com outros mamíferos não-primatas, e assim sucessivamente. Em decorrência,temos uma organização hierárquica onde os humanos estão incluídos no grupo dos primatas, que, por sua vez está incluído no grupo dos mamíferos (h p m), estabelecendo, assim,uma seriação.

Podemos observar que há relações entre as diferentes classes, e isso é o que permite a definição de cada uma delas. Sem a estrutura de relações, não existiriam classes e, portanto, não existiria conhecimento. Por quê?A identificação de um objeto no mundo depende de sua diferenciação em relação a outros objetos. Essa diferenciação presume classificação que, por sua vez, presume seriação.O conhecimento depende da distinção entre os atributos dos objetos do mundo. Sem a distinção, sem o relacionamento,não há conhecimento. Relação define, portanto,conhecimento! Relação não é o conhecimento em si, mas um fator fundamental para que o conhecimento exista.

É necessário que tenhamos estes conceitos de relações ao estudarmos Prolog, uma vez que, através desta linguagem de programação,iremos representar o mundo através de relações. A programação em Prolog - que significa programação em lógica - não é baseada no seqüenciamento de procedimentos, mas na definição de relações,na forma com a qual se representa o mundo que se quer implementar no computador.

 3.2.   Utilização

A linguagem Prolog teve sua concebida por Colmerauer e Roussel em1972, sendo sua primeira versão feita em Fortran por Battani e Meloniem 1973. Desde então tem sido utilizada para aplicações de computação simbólica, como banco de dados relacionais,compreensão de linguagem natural, automação de projetos,análise de estruturas bioquímicas e sistemas especialistas.Como podemos perceber, esta linguagem é utilizada em tarefas que exijam a representação do conhecimento para executá-las.Aplicações como os sistemas especialistas e linguagem natural são exemplos claros da utilidade da linguagem Prolog, uma vez que necessitam de regras fornecidas pela experiência humana para que sejam eficientes. Um sistema especialista que necessite realizar um diagnóstico precisa que a experiência humana do especialista esteja inserida nas regras de produção para que produza um resultado correto.A língua natural também possui peculiaridades que somente a experiência humana pode traduzir. A estruturação de frases e a semântica inerente a elas é fruto do aprendizado,que, até o momento, somente o homem pode reproduzir em regras.

 3.3.   Características Básicas

 3.3.1.      Comandos

 Fatos:

Baseiam-se no seguinte comando:

 predicado(arg 1[,arg 2,...,arg n]).

 onde:

 predicado = relação;

arg i = objetos sobre os quais atuam a relação.

 Um exemplo simples de fato com dois argumentos seria a relação amiga:

 amiga(joana, maria).

 Assim, definimos uma relação (amizade) entre dois argumentos (joana e maria). Obviamente podemos ter um fato com apenas um argumento:

 homem(carlos).

 Observe que a relação (homem), quando submetida a apenas um objeto (carlos), torna-se como uma característica daquele objeto.

No caso de haver mais argumentos, a ordem de consistência é definida como sendo a atualmente registrada, ou seja, a última utilizada. Por exemplo:

 paga(patrao,salario,empregado).

 À primeira vista parece evidente que a relação(paga) é direta entre os dois primeiros objetos (patrao,salario)e indireta ao terceiro (empregado). Esse tipo de interpretação está dependente da ordenação dos objetos declarados nos fatos. Para manter uma coerência, é importante que todos os fatos sigam uma mesma estrutura de interpretação. Assim,no caso da relação amiga talvez fosse mais prudente a declaração bivalente:

 amiga(joana,maria).

amiga(maria,joana).

 demonstrando a relação amiga tanto no sentido do objeto joana-maria quanto maria-joana.

É importante salientar que um conjunto de fatos forma um banco de dados. Ou seja, várias afirmações compõe os dados existentes no sistema. Os fatos são a célula básica do banco de dados Prolog.

 Questões:

A sintaxe de questões varia de acordo com os compiladores existentes, mas basicamente é um fato antecedido de um ponto de interrogação ou comando que indique a formulação de uma questão. Por exemplo:

 ?- amiga(joana, maria).

yes

 A função da questão é basicamente testar a existência de uma relação sobre os argumentos propostos.No exemplo apresentado, a resposta (yes) foi positiva.

 Variáveis

As variáveis são utilizadas de acordo com o comando:

 pred(arg 1,Var 1[,arg/Var 2,...,arg/Var n]).

Ou

pred(Var 1,arg 1[,arg/Var 2,...,arg/Var n]).

onde Var = variável (deve começar com letra maiúscula).

A variável é o elemento mais útil para a formulação de questões. Por exemplo:

 ?- amiga(joana,Quem).

Quem = maria

 Em cima das questões com variáveis é que serão construídos os sistemas mais complexos de estruturação da representação de conhecimentos.

 Conjunção

A conjunção permite a composição de fatos, formando o chamado e lógico: 

pred(arg/Var,arg/Var),pred(arg/Var,arg/Var). 

A vírgula ( , ) entre as questões determinam a validade mútua de ambas (ou do conjunto envolvido). O objetivo da conjunção é a realização de uma busca no banco de dados para a verificação da validade da questão ou busca dos objetos relacionados com os argumentos propostos. O mecanismo de busca é uma estratégia de controle do tipo busca em profundidade com retrocesso .

Assim, dados os seguintes fatos:

 amiga(joana,maria).

amiga(clara,maria).

 E a questão:

 ?- amiga(joana,Quem),amiga(clara,Quem).

Quem = maria

 Como podemos perceber, a consulta teve como objetivo obter o objeto (maria) que faltava para validar a conjunção entre os dois objetivos. Objetivo, neste contexto, é cada um dos componentes da conjunção.

Há duas formas de validação de conjunções.A primeira ocorre caso todos os objetivos sejam fatos como, por exemplo:

 ?- amiga(joana,antonia),amiga(maria,clara).

no

 todos os objetivos devem ser validados. A segunda forma ocorre, como já foi visto, caso hajam variáveis e existam correspondentes entre as variáveis dos objetivos.

Para facilitar a compreensão da construção da conjunção, ela pode ser vista como uma estrutura em árvore,onde os objetos são folhas das relações, as quais estão ligadas à questão, que torna-se a raiz da árvore.No caso da questão cujo objeto maria era a incógnita, temos como nodos filhos da raiz os predicados da relação amiga,e de cada relação partem duas folhas com os objetos. Através da varredura desta árvore podemos deduzir que a variável Quem referia-se ao objeto maria , comum em ambos objetivos,tornando, deste modo, a questão válida.

 Regras:

É o comando na forma: 

pred(arg/Var,arg/Var) :- pred(arg/Var,arg/Var). 

onde o símbolo :- indica a uma condição (se) ou, no caso de compararmos com lógica de predicados, a uma implicação material.

A regra é o instrumento básico de organização do banco de conhecimentos e permite a construção de questões complexas (formas mais sofisticadas de consulta ao banco de dados). É ela que indica a validade de um conjunto de fatos, questões ou conjunções.

Para exemplificar o uso da regra, vamos inicialmente declarar alguns fatos:

 amiga(joana,maria).

amiga(maria,joana).

amiga(maria,clara).

amiga(clara,maria).

ama(joana,maria).

ama(maria,joana). 

E a regra amiga_intima: 

amiga_intima(Ela1,Ela2) :-

amiga(Ela1,Ela2),

amiga(Ela2,Ela1),

ama(Ela1,Ela2),

ama(Ela2,Ela1).

 Desta forma, seria verdadeira a questão:

 ?- amiga_intima(maria,joana).

yes

 E falsa a questão:

 ?- amiga_intima(maria,clara).

no

 Percebemos, então, que há a possibilidade de construção de produções complexas em cada regra, permitindo conceitos cada vez maiores e consultas com um maior nível de definição e de variáveis.

Disjunção:

A disjunção é o oposto da conjunção,equivale ao ou lógico:

 pred(arg/Var,arg/Var) :- (pred(); atrib;...).

No exemplo:

amiga(X) :- (X=maria; X=joana).

Uma consulta seria:

?- amiga(celia).

no

Ou:

?- amiga(Quem).

Quem = maria

Quem = joana

 Como podemos observar, o ponto-e-vírgula ( ; ) permite a aceitação de uma ou outra condição para a validação da cláusula, enquanto o operador de conjunção( , ) implica na necessidade de aceitação de todas as condições.

 ·        Operadores Numéricos

 Relacionais

Operadores numéricos relacionais são aqueles usados para realizar testes quantitativos entre dois números. São eles:

 =             \=

<             >

=<           >=

 Ou seja, os símbolos são: igual (X = Y), diferente(X \= Y), menor (X < Y), maior (X > Y), menor ou igual (X =< Y) e maior ou igual (X >= Y). Observe que o símbolo menor ou igual segue a ordem inversa de seu nome. Apesar dessas afirmações, nem todos os compiladores Prolog seguem esta versão. É sempre prudente observar o manual do compilador para ver se ele segue este padrão. Há versões em que,por exemplo, a diferença é dada pelo sinal <>.

Um exemplo da utilidade dos operadores relacionais:

 dentro_interv_aberto(K,X1,X2) :-

K > X1,

K < X2.

 E a questão:

 ?- dentro_interv_aberto(5,0,5).

no

Como se vê, há diversas possibilidades de criação de regras matemáticas utilizando os operadores relacionais.

 Aritméticos

Operadores aritméticos são os utilizados na aritmética básica:

+              -

*              /

mod            is

Ou seja: soma (X + Y), subtração (X - Y), multiplicação(X * Y), divisão (X / Y), módulo (X modY) e resultado (X is equação ). Por exemplo:

ao_quadrado(X,Y) :-

Y is X*X.

E a questão:

?- ao_quadrado(6,X).

X = 36

Deste modo, percebemos que há a possibilidade de construção de diversas funções matemáticas mais complexas a partir de regras e operadores aritméticos. Há funções matemáticas mais complexas já implementadas em Prolog que serão analisadas posteriormente.

3.3.2.      Estruturas de Dados

 As estruturas de dados ocorrem na forma: 

pred(arg, estrut(atrib1,...,atrib n)). 

Ou

pred(estrut(atrib1,...,atrib n), arg).

 As estruturas servem para aninhar dados de forma organizada, estruturada.Elas permitem a distinção de atributos para um argumento.Um exemplo de uma estrutura simples seria:

 ficha(num,func(nome,end,cargo)).

Os átomos nome , end , cargo são os componentes da estrutura func , o que permite o armazenamento de atributos do funcionário de uma empresa, por exemplo. Mas podemos fazer mais níveis de alinhamento:

ficha(num,func(dados_pes(nome,end),cargo(funcao,salario(basico,descontos,vantagens)))).

Nesta linha de código temos a definição de uma ficha cadastral, cuja chave é um número que permite o acesso aos dados de um funcionário: seus dados pessoais e seu cargo na empresa. Os dados pessoais resumem-se ao nome e endereço do funcionário. O cargo compõe-se da função exercida e do salário ganho. O salário é calculado em termos do vencimento básico, descontos e vantagens acumuladas.

Obviamente, este fato deve estar com os dados já preenchidos:

ficha(001,func(dados_pes('Fulano de Tal','Rua Epa'),cargo('continuo',salario(100,30,10)))).

Assim, temos um exemplo exagerado de aplicação de estruturas Prolog. Obviamente que uma aplicação real para a produção de um sistema de gerenciamento de banco de dados teria uma maior preocupação com a clareza de distribuição dos dados, evitando o aninhamento excessivo.

Podemos ainda construir expressões com variáveis do tipo: 

pred(estrut(atrib,Var),Var). 

que podem ser representadas na forma:

   

Como podemos ver, a estrutura de dados já é criada em qualquer tipo de comando Prolog, de forma que devemos ter em mente sempre esta estruturação para mais facilmente compreendermos a função do comando que estamos analisando ou trabalhando.

Obviamente as estruturas em Prolog não resumem-se à forma de seus comandos. Podemos construir estruturas de dados mais complexas para auxiliar na implementação de aplicações.

 Listas

A forma mais adequada para estruturação de dados em Prolog são as listas. Como o nome diz, são estruturas dinâmicas de armazenamento de dados. Elas são utilizadas na construção de vetores e matrizes dinâmicos de caracteres, números, e quaisquer outros dados utilizados. Apresentam-se na forma:

 pred([elem,...],[],[elem[elem,..],...],arg,...).

 onde elem pode ser qualquer tipo sintático.

Um conceito importante para a utilização das listas é o cabeça-corpo ( | ). Ele é usado para isolar membros de uma lista. Por exemplo, dada uma lista na forma:

 lista([a,b,c,d,e]).

 Aplicando-se a questão:

 ?- lista([X | Y]).

X=a

Y=[b,c,d,e]

 Como podemos observar, o operador | permite a separação da cabeça da lista de seu corpo. Isso permite o isolamento dos membros da lista, facilitando sua análise.

As listas são tipos peculiares de estruturas de dados que também podem ser representadas através de árvores.Esse tipo de representação é muito útil quando trabalhamos com altos níveis de aninhamento. Como, por exemplo:

 ficha(123,func(dados_pes([["Silva","Fulano da"],["solteiro",0]],["ruaTal",99,301]),cargo( ["continuo"],[minimo,1]))).

 A árvore para representar esta linha de código seria: 

 Podemos observar, neste exemplo extremado, a aparente complexidade provocada pela leitura da linha de código que é desfeita quando representada em árvore. Nesta representação vemos uma nova simbologia com a visualização do ponto ( .), que faz a indicação dos nodos da lista; e da lista vazia( [] ), que indica o final da lista (NULL). Como dito anteriormente,este é um bom recurso para a visualização de expressões com um maior nível de complexidade, facilitando sua análise e compreensão.

As listas são o principal tipo de estrutura de dados em Prolog,com as quais é possível a construção de vetores e matrizes, com a vantagem de ser uma estrutura de memória dinâmica.

Programa-exemplo 1:

Verifica se uma palavra é palíndroma. Exemplo de utilização:

 inicio. <enter> madam.<enter>

% entrada e saida de dados

inicio :- read(X), (X = para; teste(X), inicio).

teste(X) :- nome(X,N), palindromo(N), write(X), write(` eh palindroma'),nl, !.

teste(X) :- write(X), write(` nao eh palindroma'), nl.

% inversao e teste

palindromo(X) :- inverte(X,X).

inverte(L1,L) :- invconc(L1,[ ],L).

invconc([H|T],L,M) :- invconc(T,[H|L],M).

invconc([ ], L, L).

 

Programa exemplo 2:

Implementa o método de ordenação da bolha.Uso: bolha([4,56,8,10],X).

 bolha(L, S) :-

concatena(U, [A, B|V], L),

B > A, !,

concatena(U, [B, A|V], M),

bolha(M, S).

bolha(L,L).

concatena([],X,X).

concatena([X|Y],Z,[X|W]) :- concatena(Y,Z,W).

 

Pilhas

Em Prolog há mais de uma forma de se implementar uma estrutura para funcionamento no método LIFO. O que é apresentado a seguir é apenas uma das formas, a de mais fácil manipulação.Ela tem a forma:

...s(elem1,elem2)... 

Onde s indica uma pilha (stack) e elem pode ser qualquer tipo sintático.Observe que há dois componentes, sendo que o primeiro indica a posição no topo da pilha e o segundo o restante (se houver) da estrutura. Os pontos antes e depois indicam que este é um trecho de predicado, ou seja,seu uso não é isolado, como demonstram os exemplos de manipulação a seguir.

Antes de utilizarmos uma pilha, temos que defini-la: 

pilha(s(X,Y),X,Y). 

Para manipularmos, podemos realizar operações de retirada e inserção de pilha. Um exemplo de retirada é o seguinte: 

?- pilha(s(a,s(b,s(c,0))),X,Y).

X = a

Y = s(b,s(c,0))

 Este predicado está retirando o topo, que é o elemento a.

Para inserção de um elemento i no topo da pilha:

 pilha(E,i,s(a,s(b,s(c,0)))).

E = s(i,s(a,s(b,s(c,0))))

 Como vimos, nos dois exemplos há as variáveis para retorno das estruturas transformadas. Observe também que são trabalhados 3 elementos no predicado: quando queremos retirar, o primeiro elemento é a pilha original, quando queremos inserir, o primeiro elemento é a pilha final.

 Filas

A representação da fila é semelhante à da pilha. Ela tem a forma: 

...q(elem1,elem2)... 

Onde q indica uma fila (queue) e elem qualquer tipo sintático.Como na pilha, há dois componentes, sendo que o primeiro indica o primeiro da fila e o segundo representa o restante (se houver) da estrutura.Os pontos antes e depois indicam que este é um trecho de predicado,como veremos nos exemplos a seguir.

Podemos manipular a fila através da inserção ou retirada. A retirada se dá no início da fila, e a inserção no fim. Para definir a retirada, devemos estabelecer o seguinte predicado: 

retira_fila(q(E,Re),E,Re). 

Um exemplo de utilização desta definição é o seguinte: 

?- retira_fila(q(a,q(b,q(c,0))),a,F).

F = q(b,q(c,0))  

Neste exemplo, desejou-se retirar o elemento a da estrutura, que era o primeiro da fila. A fila alterada é retornada em F.

A definição de inserção é a seguinte: 

insere_fila(F,q(R,E),R,F). 

Na fila a seguir, irá ser inserido um novo elemento, d,que irá para o final da fila:

?- insere_fila(q(a,q(b,q(c,X))),X,d,F).

F = q(a,q(b,q(c,q(d,X)))) 

Correntes

Nesta seção, entende-se correntes por listas ligadas.O nome foi alterado por não serem listas ligadas, mas sim um recurso para representar uma estrutura com o mesmo princípio.

As correntes tem uma forma semelhante à pilha e fila: 

...c(elem1,elem2)... 

Onde c indica uma corrente (chain) e elem qualquer tipo sintático.Como na pilha e na fila, há dois componentes, sendo que o primeiro indica o primeiro elemento da corrente e o segundo representa o restante(se houver) da estrutura.

A seguir, veremos exemplos de retirada, inserção e inversão de lista. Para retirada, temos a seguinte definição: 

retira_lista(A,c(A,X),X) :-!.

retira_lista(A,c(X,Y1),c(X,Y2)) :- retira_lista(A,Y1,Y2). 

Um exemplo de retirada de um elemento do meio da corrente: 

? - retira_lista(b,c(a,c(b,c(c,nil))),L).

L = c(a,c(c,nil))  

Para inserção, temos a definição: 

insere_lista(A,B,c(B,X1),c(A,c(B,X1))).

insere_lista(A,B,c(C,X),c(C,X1)) :- insere_lista(A,B,X,X1). 

Exemplificando, para inserir um elemento entre outros dois:

 ? - insere_lista(b,c,c(a,c(c,nil)),L).

 L = c(a,c(b,c(c,nil)))

 Uma outra definição auxiliar é a de inversão de corrente: 

inverte_lista(X,X,Z,Z).

inverte_lista(c(U,V),X,Y,Z) :- inverte_lista(V,X,c(U,Y),Z). 

A utilização da definição de inversão é como mostrado a seguir: 

? - inverte_lista(c(a,c(b,c(c,x))),x,x,Z).

Z = c(c,c(b,c(a,x))) 

Árvores

Como nas estruturas analisadas anteriormente, é possível uma representação de árvore em Prolog que não é semelhante às linguagens procedurais, que utilizam procedimentos.Uma forma de representação de árvore binária é a seguinte:

...t(ramo1,raiz,ramo2)... 

Onde t indica uma estrutura em árvore (tree), ramo1 representa a derivação à esquerda da árvore e ramo2 é a derivação à direita.

Uma operação possível em árvore é a troca de uma subárvore por outra. A definição de troca de subárvore é: 

troca_sub(A,S,S,A).

troca_sub(A,S,t(T1,X,R2),t(T1,X,R1)):- troca_sub(A,S,T1,T2).

troca_sub(A,S,t(T1,X,R1),t(T1,X,R2)):- troca_sub(A,S,R1,R2). 

A execução desta definição pode ser demonstrada através da questão: 

? - troca_sub(t(o,k,p),t(h,d,i),t(t(t(h,d,i),b,e),a,t(f,c,g)),A).

A = t(t(t(o,k,p),b,e),a,t(f,c,g)) 

Neste exemplo, a subárvore com raiz no elemento d foi substituída por outra, com raiz k. A variável A apresenta a nova árvore.

Estas foram as estruturas básicas de dados que são manipulados em Prolog. Para as formas apresentadas de pilha, fila, corrente e árvore possuem determinadas letras definindo as estruturas. Estas letras são apenas indicadores, podendo ser trocadas por outras letras.Por outro lado, as letras apresentadas (s, q, c, t) são convenções para melhor situar o programador em que tipo de estrutura está sendo manipulada.

Outra observação pertinente é lembrar que estas últimas estruturas são variações de utilização da estrutura elementar, apresentada no início desta seção de estruturas de dados. Vale revisar as estruturas variantes para melhor compreender seu funcionamento. 

Corte de Fluxo

Há ocasiões em que, por um ou outro motivo, não desejamos a continuidade da verificação das regras. Para estes casos usamos um mecanismo para corte do fluxo, simbolizado por um ponto de exclamação ( ! ). A seguir veremos três casos em que o corte é utilizado. 

Escolha de uma regra

O corte é útil quando sabemos que uma determinada regra é a última a ser analisada e haveria problemas na continuidade de verificação das demais regras. Por exemplo, uma implementação de função fatorial: 

fat(0,1) :- !.

fat(N,R) :- Ni is N-1, fat(Ni,Ri), R is Ri*N. 

Observe que a última cláusula válida na recursão é a do fatorial de zero. Esta última cláusula é marcada pelo corte, informando que não é necessária a continuidade do cálculo. 

Programa-exemplo 3:

Verifica igualdade entre duas listas. Exemplo: verifica_igual([a,3,1,d,v],[a,d,v,1,3]). 

verifica_igual(X, X) :- !.

verifica_igual(X, Y) :- igual(X, Y).

% faz a troca

igual([X|L1],[Y|L2]) :- retira(X, L2, L3),

igual(L1, L3).

igual([],[]).

% compara por eliminacao

retira(X,[Y|L1],[Y|L2]) :- retira(X,L1,L2).

retira(X,[X|Y],Y).

 

Programa-exemplo 4:

Subtração de duas listas. Exemplo de uso: subtract([a,c,b],[a,b],X).

subtrai(L,[],L) :- !.

subtrai([H|T]) :- membro(H,L), !, subtrai(T,L,U).

subtrai([H|T],L,[H|U]) :- !, subtrai(T,L,U).

subtrai(_,_,[]).

membro(Cab,[Cab|_]).

membro(Elem,[_|Corpo]) :- membro(Elem,Corpo).

 

Combinação corte-falha

Para acelerar a avaliação de regras (e desta formatambém economizar memória), podemos combinar o corte como predicado fail . Este é um predicado interno ao Prolog que força um retrocesso na avaliação das regras,como se indicasse uma falha. O predicado em si não tem muito sentido,mas quando utilizado com o corte, mostra-se muito útil. Por exemplo: 

calculo_medias(X,Y,Z,R):- R = X*0.5+Y*0.3+Z*0.2, R < 1000, !, fail.

calculo_medias(X,Y,Z,R):- R = X*0.3+Y*0.5+Z*0.2, R < 5000, !, fail.

calculo_medias(X,Y,Z,R):- R = X*0.2+Y*0.3+Z*0.5. 

Aqui temos três formas de realizar um determinado cálculo,com fatores aplicados dependendo da faixa do resultado. Poderia ser, por exemplo, uma espécie de cálculo salarial. Caso o valor da primeira regra fosse menor que 1000, então não seria necessária a investigação das demais regras, uma vez que este é o cálculo correto. O mesmo vale para a segunda regra, restando apenas a opção da terceira. 

Geração e Teste

Mais uma utilidade do corte é possibilitar o encerramento de implementações com infinitas avaliações de regras. Em programação convencional isto seria chamado de laço infinito, e em Prolog a recursão também faz parte da avaliação das regras. Há programas que correm o risco de executarem indefinidamente se não houver um teste deparada. Isso ocorre na seguinte implementação: 

inteiro(0).

inteiro(N) :- inteiro(K),N is K+1.

divisao(N1,N2,R):- inteiro(R),

P1 is R*N2,

P2 is (R+1)*N2,

P1 =< N1, P2> N1, !. 

A função deste código é realizara divisão inteira de dois números, embora seja uma implementação não-eficiente. Esta implementação usa um gerador de números inteiros (predicado inteiro) e testes de compatibilidade.Quando os testes são concordantes, o resultado é devolvido.A lógica é simples: vão sendo gerados todos os números inteiros até que se encontre um que, multiplicado pelo divisor,seja igual ou maior que o dividendo. 

3.3.3.      Predicados Incorporados  

Prolog está provido de predicados básicos que possibilitam uma estrutura mínima de funcionamento, como os de entrada e saída, consulta e inserção . Aqui é importante observar que estes predicados podem variar de uma versão para outra de compiladores/interpretadores Prolog. Os predicados aqui apresentados são de uma versão standard, o que implica que poderão não existir ou estarem implementados sob diferentes formas em outras versões mais sofisticadas de Prolog. 

Entrada e Saída  

get0(X) / get(X) 

São os predicados utilizados para leitura de caracteres escritos no teclado e comparação com a variável X. A diferença entre ambos é que get0 pega todos os caracteres digitados e get salta os caracteres não-imprimíveis. Sua utilização,no entanto, é restrita à leitura de um caractere por vez. 

read(X) 

Este predicado destina-se à leitura de termos do teclado.Todo o termo escrito deve terminar com um ponto ( . ). Este termo pode ser uma palavra isolada, uma lista de letras ou ainda um string de caracteres.Por exemplo, dada a regra: 

oi :- read(X), write('Resposta: '), write(X). 

E a questão: 

?- oi.

ola.

Resposta: ola 

O termo ola é encarado aqui como um argumento que será associado à variável X. Por outro lado: 

?- oi.

Ola.

Resposta: _00A0 

Isso demonstra que Ola, neste contexto, foi considerado uma variável que está sendo associada à X. Já no caso de lista de caracteres, temos: 

?- oi.

"Ola, como vai?".

Resposta: [79,108,97,44,32,99,111,109,111,32,118,97,105,63] 

Neste caso, o termo é uma lista com os caracteres que estão entre aspas. Se ao invés de aspas colocarmos apóstrofos,teremos outro tipo de termo:

?- oi.

'Ola, como vai?'.

Resposta: Ola, como vai? 

Vemos, assim, que há diversas variantes para a utilização do read, dependendo da aplicação que temos em vista. 

put(X) 

Imprime apenas um caractere por vez. A variável X deve conter um inteiro correspondente ao caractere que se quer imprimir. 

write(X) 

Imprime termos associados a X. São válidas as mesmas observações feitas para read. Por exemplo, seja a regra: 

esc(X) :- write(ola), nl, write(X), nl, write("Ola"),nl, write('Ola'). 

E a questão: 

?- esc(ola).

Ola

ola

[79,108,97]

Ola 

display(X) 

Tem funções idênticas a write, porém distingue os temos sintáticos. Por exemplo, dada a regra: 

esc2 :- write(a+b*c), tab(5), display(a+b*c), nl, write([oi,oi]),tab(5), display([oi,oi]). 

E a questão:

?-esc2.

a + b *c '+'(a,'*'(b,c))

[oi,oi]'.'(oi,'.'(oi,[])) 

Vemos, pois, que o predicado display apresenta os termos na ordem de tratamento que Prolog dá a eles. 

nl 

Provoca a quebra da linha, sendo que o próximo comando de impressão iniciará numa nova linha. 

tab(X) 

Coloca uma quantidade de espaços em branco determinada pelo valor de X. 

skip(X) 

Processa até que o caractere igual a X seja encontrado. 

Manipulação de Arquivos 

tell(X) 

Cria um arquivo de saída X. 

telling(X) 

Verifica se X é o atual arquivo de saída . 

told 

Fecha o arquivo de saída aberto. 

see(X) 

Abre um arquivo de entrada existente. 

seeing(X) 

Verifica se X é o atual arquivo de entrada . 

seen 

Fecha o arquivo de entrada aberto. 

Consulta e Inserção 

consult(X) 

Realiza uma consulta a um determinado banco de dados X. Esta consulta consiste em carregar o conteúdo de X para a memória. Uma vez consultado o banco de dados, os fatos e regras nele contidos poderão ser mencionados. O banco de dados X geralmente é um arquivo texto: 

?-avo(Avo,gabriel).

No

?-consult('familia.ari').

yes

?-avo(Avo,gabriel).

Avo= fulgencio 

Nesse exemplo, a primeira questão não foi possível ser respondida pela falta do banco de dados 'familia.ari'. Uma vez consultado,ele fica residente na memória e a questão pode ser respondida. 

reconsult(X) 

É um predicado complementar a consult . Ele tem a função de trocar fatos e regras que tenham sido modificados de um banco de dados X residente na memória (já consultado).O predicado consult pode ser usado para a releitura do banco dedados, mas ele será replicado na memória, o que promoverá um erro de avaliação das regras. O predicado reconsult tem a vantagem de alterar apenas o que foi modificado no arquivo do banco de dados. 

listing(A) 

Permite a listagem de uma determinado predicado que está sendo utilizado. Por exemplo: 

?- listing(avo).

avo(Avo,Neto):-

macho(Avo),

casal(Avo,_),

filho(Filho,Avo),

pai(Filho,Neto). 

asserta(X) / assertz(X) 

Estes predicados servem para inserir um novo fato X no banco dedados residente na memória. (Observe que a alteração é somente a nível de memória). A diferença entre ambos é que asserta insere o fato no início do banco de dados e assertz faz a inserção ao final do banco. por exemplo:

?- consult('familia.ari').

yes

?- listing(filha).

filha(juliana,marcos).

filha(juliana,mariana).

?- asserta(filha(marcia,marcos)).

yes

?- assertz(filha(marcia,mariana)).

yes

?- listing(filha).

filha(marcia,marcos).

filha(juliana,marcos).

filha(juliana,mariana).

filha(marcia,mariana). 

Nesse exemplo, foi inserido um fato no início do banco dedados e outro no final. Na última listagem podemos observar os fatos já existentes junto aos novos. 

retract(X) 

Realiza a retirada de um fato X do banco de dados existente na memória.É o oposto a asserta / assertz . 

Negação 

not(X) 

É o predicado em que tem sucesso quando X falha. Por exemplo: 

?- not(macho(mariana)).

yes

Repetição 

repeat 

Realiza a repetição da regra até que a próxima cláusula seja válida. Por exemplo, dada a regra: 

pega2(X) :- repeat, get0(X).

pega(X) :- pega2(X), X > 32, !.

A regra pega será repetida até que a entrada seja um caractere imprimível. 

3.3.4.      Regras Gramaticais  

Prolog provê recursos especiais para facilitar a implementação de regras gramaticais de gramáticas livres de contexto.

Para exemplificar, podemos estruturar as regras de frases simples em português: esta frase divide-se em sintagma nominal e sintagma verbal; o sintagma nominal pode ser formado por um artigo e por um substantivo;o sintagma verbal pode ser formado por apenas um verbo ou por um verbo e outro sintagma nominal. Na forma Prolog que vimos até agora, isso poderia ser descrito assim: 

frase(S0,S) :- sn(S0,S1), sv(S1,S).sn(S0,S) :- art(S0,S1), subs(S1,S).

sv(S0,S) :- verbo(S0,S).

sv(S0,S) :- verbo(S0,S1), sn(S1,S).

art([o|S],S).

art([a|S],S).

subs([homem|S],S).

subs([flor|S],S).

verbo([colhe|S],S).

verbo([canta|S],S). 

Ou, na forma facilitada: 

frase --> sn, sv.

sn --> art, subs.

sv --> verbo.

sv --> verbo, sn.

art --> [o].

art --> [a].

subs --> [homem].

subs --> [mulher].

subs --> [flor].

verbo --> [colhe].

verbo --> [canta]. 

Uma questão poderia ser: 

?- frase([a,mulher,colhe,a,flor],[]).

yes 

Como vemos, a construção de gramáticas torna-se simplificada e clara, conforme a notação usual. Prolog é,portanto, uma ferramenta por excelência para a estruturação de gramáticas, possibilitando a implementação de linguagens para diversas aplicações.