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.
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.
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
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.