Escolar Documentos
Profissional Documentos
Cultura Documentos
1 – Construções Básicas
As construções básicas da programação em lógica, termos e frases (statements), são
inerentes à lógica. Existem 3 tipos de frases básicas: factos, regras e consultas.
Há só uma estrutura de dados: o termo lógico.
1.1. FACTOS
O tipo de frase mais simples é o facto. Os factos são meios de dizer que existe uma
relação entre objectos.
ex: father(abraham,isaac)
Um outro nome para uma relação é predicado. Nomes de indivíduos são conhecidos
como átomos.
A relação plus (mais) pode ser definida à custa de factos, através duma tabela longa,
como por exemplo plus(0,0,0). plus (0,1,1). etc.
Os predicados e átomos nos factos começam por letra minúscula.
Um conjunto finito de factos constitui um programa lógico. É também a descrição de
uma situação. Este ponto de vista é a base da programação de bases de dados.
ex: de base de dados de uma relação familiar:
father(terach,abraham). male(terach).
father(terach,nachor). male(abraham).
father(terach,haran). male(nachor).
father(abraham,isaac). male(haran).
father(haran,lot). male(isaac).
father(haran,milcah). male(lot).
father(haran),yiscah).
female(sarah).
mother(sarah,isaac). female(milcah).
female(yiscah).
(Program 1.1.)
1.7. REGRAS
Queries conjuntivas interessantes definem, elas próprias, relações.
ex: father(terach,X), father(X,Y)? questiona sobre os netos de terach.
Isto leva-nos à terceira, e mais importante, frase na programação em lógica – regra –
que nos permite definir novas relações em termos de relações existentes:
São da forma A <-- B1,B2,...,Bn.
A é a cabeça da regra e a conjunção de metas Bs são o corpo da regra.
Regras, factos e queries são também chamadas de cláusulas Horn ou,
simplesmente, cláusulas. De notar que um facto é um caso especial de uma regra
quando n=0. Os factos são também chamados cláusulas unitárias.
Se n=1 é chamada cláusula iterativa
tal como para os factos, as variáveis que aparecem nas regras são universalmente
quantificados e o seu alcance é toda a regra.
ex: son(X,Y) <--father(Y,X), male(X). ou, no meu Prolog :- em vez de <--
daughter(X,Y) <-- father(Y,X), female(X).
grandfather(X,Y) <-- father(X,Z), father(Z,Y).
Cap. 2 – PROGRAMAÇÃO DE BASES DE DADOS
Há 2 estilos básicos de usar programas lógicos: definindo uma base de dados lógica e
manipular estruturas de dados.
Uma base de dados lógica contém um conjunto de factos e regras.
Veremos como um conjunto de factos pode definir relações, como nas bases de dados
relacionais, e como as regras podem definir complexas consultas relacionais, como na
álgebra relacional.
uncle(Uncle,Person) <--
brother(Uncle, Parent), parent(Parent, Person).
sibling(Sib1, Sib2) <--
parent(Parent, Sib1), parent(Parent, Sib2), Sib1≠Sib2.
cousin(Cousin1, Cousin2) <--
parent(Parent1, Cousin1), parent(Parent2,Cousin2), sibling(Parent1, Parent2).
(Programa 2.1.)
com:
resistor(power,n1).
resistor(power,n2).
transistor(n2,ground,n1).
transistor(n3,n4,n2).
transistor(n5,ground,n4).
inverter(Input,Output) <--
transistor(Input, ground, Output),
resistor(power,Output).
nand_gate(Input1,Input2,Output) <--
Output is the logical nand of Input1 e Input2.
nand_gate(Input1,Input2,Output) <--
transistor(input1,X,Output), transistor(Input2, ground, X),
resistor(power,Output).
and_gate(Input1,Input2,Output) <--
Output is the logical and of Input1 and Input2
and_gate(Input1,Input2,Output) <--
nand_gate(Input1,Input2,X),
inverter(X,Output).
Programa 2.2.
A primeira linha da definição (ex: nand_gate) é apenas o esquema de relação para o
procedimento. Isto enfatiza a leitura descritiva dos programas.
ex: de consulta and_gate(In1,In2,Out)?
ex:
a) course(complexity, monday, 9,11,david,harel,feinberg,a).
b) course(complexity,time(Monday,9,11),lecturer(david,harel),location(feinberg,a)).
As regras que não usem valores particulares de um argumento estruturado não
precisam saber como o argumento está estruturado. Ver ex: abaixo no prog.2.4. no
caso de time.
Não há regras definitivas: Não usar dados estruturados conduz a uma maior
uniformidade de representação quando os dados são simples. As vantagens da
estruturação dos dados são a compactação da representação, que mais
apuradamente reflecte a nossa perspectiva da situação e modularidade.
lecturer(Lecturer,Course) <--
course(Course,Time,Lecturer,Location).
duration(Course,Length) <--
course(Course,time(Day,Start,Finish),Lecturer,Location),
plus(Start,Length,Finish).
teaches(Lecturer,Day) <--
course(Course,time(Day,Start,Finish),Lecturer,Location).
ocupied(Room,Day,Time) <--
course(Course,time(Day,Start,Finish),Lecturer,Room),
Start ≤ Time, Time ≤ Finish.
Programa 2.4.
list([a,b,c])
list([b,c])
list([c])
list([ ])
Prog. 3.12.
member(Element, List) <--
member(X,[X|Xs]).
member(X,[Y|Ys]) <-- member(X,Ys).
alternativamente:
member(X,[X|Xs]) <-- list(Xs)
Este programa tem muitos usos como por exemplo: verificar se um elemento está
numa lista; encontrar um elemento duma lista; encontrar uma lista que contém um
elemento.
X denota a cabeça da lista Xs denota a cauda. Mais em geral variáveis no plural
denota listas de elementos e nomes singulares denota elementos individuais. Sufixos
numéricos denotam variantes de listas.
Prog. 3.13.
prefix(Prefix, List) <--
prefix([ ], Ys).
prefix([X|Xs], [X|Ys]) <-- prefix(Xs,Ys).
Uma sublista arbitrária pode ser dada em termos de sufixos de prefixos ou prefixos de
sufixos, como mostra o programas seguinte:
este programa expressa a regra lógica que Xs é uma sublista de Ys se existe Ps tal
que Ps é um prefixo de Ys e Xs é um sufixo de Ps. O programa seguinte é dual:
O predicado prefix também pode ser usado como base para uma definição recursiva
de sublista (prog. 3.14c.) A regra básica diz que um prefixo de uma lista é uma sublista
da lista. A regra recursiva diz que a sublista de uma cauda de uma lista é uma sublista
da lista.
O predicado member pode ser visto como um caso especial de uma sublista definida
pela regra: member(X,Xs) <-- sublist([X],Xs).
sublist(Sub,List) <--
sublist(Xs,Ys) <-- prefix(Ps,Ys), suffix(Xs,Ps).
sufixo de um prefixo
sublist(Xs,Ys) <-- prefix(Xs,Ss), suffix(Ss,Ys).
prefixo de um sufixo
sublist(Xs,Ys) <-- prefix(Xs,Ys).
sublist(Xs,[Y|Ys]) <-- (Xs,Ys).
Definição recursiva de uma sublista
A operação básica com listas é concatenar duas listas para dar uma terceira lista:
Prog. 3.15.
append(Xs,Ys,XsYs) <--
append([ ], Ys, Ys).
append([X|Xs], Ys, [X|Zs]) <-- append(Xs,Ys,Zs).
Há muitos usos para append. O mais básico é concatenar 2 listas pondo uma query
como: append([a,b,c],[d,e],Xs)?
Uma query como append(Xs, [c,d],[a,b,c,d])? encontra a diferença (esta não é
simétrica como em plus) Xs=[a,b]
Program 3.16.
reverse(List,Tsil) <--
reverse([ ],[ ]).
reverse([X|Xs],Zs) <-- reverse(Xs,Ys), append(Ys,[X],Zs).
(naive reverse)
Program 3.17.
lenght(Xs,N) <--
lenght([ ], 0).
lenght(X|Xs], s(N)) <-- lenght(Xs,N).
Programa 3.18.
delete(List,X,HasNoXs) <--
delete([X|Xs],X,Ys) <-- (Xs,X,Ys).
delete([X|Xs],X,[X|Ys]) <-- X≠Z, delete(Xs,Z,Ys).
delete([ ],X,[ ]).
Uma variante é não pormos X≠Z. Mas isso tem um significado menos natural, pois
qualquer número (1,2,3,etc.) de ocorrências de X será apagado.
Prog.3.19.
select(X,HasX,OneLessXs) <--
select(X,[X|Xs],Xs).
select(X,[Y|Ys],[Y|Zs] <-- select(X,Ys,Zs).
Uma especificação lógica de ordenar uma lista é encontrar uma permutação ordenada
duma lista.
sort(Xs,Ys) – ordem ascendente - <-- permutation(Xs,Ys), ordered(Ys).
Agora temos de definir permutation e ordered.
Testar se uma lista está ordenada:
ordered([X]).
ordered([X,Y|Ys]) <-- X ≤ Y, ordered([Y|Ys]).
A permutação é mais delicada: uma maneira é seleccionar um elemento ao calhas
para ser o 1º elemento da lista permutada, e depois, recursivamente, permutar o resto
da lista:
permutation(Xs,[Z|Zs]) <-- select(Z,Xs,Ys), permutation(Ys,Zs).
permutation([ ], [ ]).
Uma outra maneira procedimental é permutar recursivamente a cauda da lista e inserir
a cabeça numa posição arbitrária:
permutation([X|Xs],Zs) <-- permutation(Xs,Ys), insert(X,Ys,Zs).
permutation([ ],[ ]).
com insert(X,Ys,Zs) <-- select(X,Zs,Ys).
Prog.3.20.
versão naive/permutações – paradigma “gerar-e-testar”
sort(Xs,Ys) <--
sort(Xs,Ys) <--permutation(Xs,Ys), ordered(Ys).
permutation(Xs,[Z|Zs]) <-- select(Z,Xs,Ys), permutation(Ys,Zs).
permutation([ ], [ ]).
ordered([ ]).
ordered([X]).
ordered([X,Y|Ys]) <-- X≤Y, ordered([Y|Ys]).
Programa 3.22.
quicksort(Xs,Ys) <--
(a lista Ys é uma permutação ordenada da lista Xs)
quicksort([X|Xs],Ys) <--
partition(Xs,X,Littles,Bigs),
quicksort(Littles,Ls),
quicksort(Bigs,Bs),
append(Ls,[X|Bs],Ys).
quicksort([ ],[ ]).
partition([X|Xs],Y,[X|Ls],Bs) <-- X≤Y, partition(Xs,Y,Ls,Bs).
partition([X|Xs],Y,Ls,[X\Bs] <-- X > Y, partition(Xs,Y,Ls,Bs).
partition([ ],Y,[ ],[ ]).
A regra recursiva para quicksort lê-se: “Ys é uma versão ordenada de [X|Xs] se Littles
e Bigs forem o resultado de particionar Xs de acordo com X; Ls e Bs são o resultado
de ordenar Littles e Bigs recursivamente; e Ys é o resultado de appending [X|Bs] a Ls.”
A leitura declarativa da primeira cláusula de partition é: “Particionar uma lista cuja
cabeça é X e cuja cauda é Xs, de acordo com um elemento Y dá as listas [X|Littles] e
Bigs se X é menor ou igual a Y, e particonar Xs de acordo com Y dá as listas Littles e
Bigs.”
II – A Linguagem Prolog
Para implementar uma linguagem de programação prática e baseada no modelo
computacional da programação lógica, três aspectos precisam de atenção_
Resolver as escolhas que permanecem na interpretação abstracta para os programas
lógicos (ver cap.4)
Enriquecer as expressividades da computação pura do modelo, acrescentando
facilidades meta-lógicas e extra-lógicas.
Aceder a algumas capacidades do computador, como aritmética rápida e prover
input/output.
6 – Prolog Puro
É um programa lógico no qual uma ordem é definida, quer para as cláusulas (clauses)
quer para as metas (goals) no corpo da cláusula.
O interpretador está preparado para tirar vantagens da ordenação da informação.
O Prolog puro é uma realização aproximada da programação lógica do modelo
computacional, numa máquina sequencial.
son(X,haran)?
father(haran, X) X=lot
male(lot)
true
Output: X=lot
;
father(haran,X) X=milcah
male(milcah) f
father(haran, X) X=yiscah
male(yiscah) f
no more solutions
A maior diferença no uso das estruturas de dados vem da natureza das variáveis
lógicas. Elas referem-se a indivíduos em vez de localizações de memória. Não suporta
pois assignment destrutivo.
O que vimos até aqui não responde à questão mais interessante: Como se pode
comparar programar em Prolog com programar nas convencionais linguagens de
programação. É o que iremos ver até ao final do livro.
CAP. 7
Programação em Prolog Puro
O maior objectivo da programação em lógica é permitir ao programador programar a
um alto nível. Idealmente deveríamos escrever axiomas que definem as relações
desejadas, mantendo ignorância do caminho que serão usadas pelo mecanismo de
execução.
As linguagens correntes, como o Prolog, ainda estão longe desse objectivo ideal da
programação declarativa.
Programas lógicos efectivos requerem conhecimento de como os mecanismos de
execução fazem as escolhas.
Este capítulo discute as consequências do modelo de execução do Prolog para o
programador.
Quando a árvore de procura para uma dada meta tem ramificações infinitas, a ordem
das cláusulas determina se alguma solução é dada ou não.
ex: append(Xs,[c,d],Ys)? em 5.4. não dá nada, mas se mudarmos dá uma infinidade.
7.2. Terminação.
O atravessamento em profundidade das árvores de procura tem um sério problema.
Se a procura de uma meta em respeito a um programa, tem derivações infinitas, a
computação não pára. Prolog pode falhar a procura de uma solução mesmo sabendo
que a meta tem computação finita.
A não terminação cresce com as regras recursivas.
Ex: acrescento
married(Male,Female)
um facto seria:
married(abraham,sarah)
como a ordem não interessa, acrescentaríamos:
married(X,Y) <-- married(Y,X)
Mas então nenhuma computação envolvendo married terminaria.
ex:
married(X,Y) <--married(Y,X).
married(abraham, sarah).
married(abraham,sarah)
married(sarah,abraham)
married(abraham,sarah)
married(sarah,abraham)
....
Regras recursivas que têm uma meta recursiva como primeira meta são conhecidas
como regras recursivas à esquerda, que são inerentemente causadoras de problemas
em Prolog, pois causam computações não terminais se chamadas com argumentos
impróprios. O melhor é evitá-las.
As relações comutativas (como o caso anterior) são melhor manejáveis definindo um
novo predicado que tem uma cláusula para cada permutação dos argumentos. No
nosso caso poderíamos resolver com:
Uma outra garantida forma de gerar programas não termináveis é usar definições
circulares, como por ex:
parent(X,Y) <-- child(Y,X).
child(X,Y) <-- parent (Y,X).
parent(haran,lot)?, não termina.
Em contraste com a ordem das cláusulas, a ordem das metas pode determinar se a
computação termina.
Ex:
ancestor(X,Y) <-- parent(X,Z), ancestor(Z,Y).
Se as metas no corpo forem alteradas, o programa ancestor torna-se recursivo à
esquerda e não termina.
Uma regra heurística para a ordem das metas no caso de programas recursivos com
testes tais como comparações aritméticas ou determinando onde duas constantes são
diferentes: Colocar os testes o mais cedo possível.
Um exemplo é a partition
partition([X|Xs],Y,[X|Ls9,Bs) <-- X ≤ Y, partition(Xs,Y,Ls,Bs).
No Prolog, em contraste com as outras linguagens de programação, a ideia é falhar o
mais cedo possível.
Uma maneira de fazer a redundância aparecer é cobrir o mesmo caso com várias
regras.
Ex:
minimum(X,Y,X) <-- X ≤ Y.
minimum(X,Y,Y) <-- Y ≤ X.
a consulta minimum(2,2,M)? tem duas soluções, sendo uma redundante.
Uma solução seria, após exame minucioso, alterar a segunda regra para
minimum(X,Y,Y) <-- Y < X.
Uma outra maneira de introduzir redundância aparece em programas que têm muitos
casos especiais.
Se adicionássemos um facto extra append(Xs,[ ],Xs) , isso aconteceria.
Para remover redundância, cada uma das outras cláusulas para append teria de cobrir
apenas listas com, pelo menos, um elemento como segundo argumento.
ex:
merge(Xs,Ys,Zs) <--
Zs é uma lista ordenada de inteiros obtida de mergir as listas ordenadas Xs e
Ys
merge([X|Xs], [Y|Ys],[X|Zs]) <-- X < Y, merge(Xs,[Y|Ys],Zs).
merge([X|Xs],[Y|Ys],[X,X|Zs]) <-- X=:=Y, merge(Xs,Ys,Zs).
merge([X|Xs], [Y|Ys], 8Y|Zs]) <-- X > Y, merge([X|Xs], Ys,Zs).
merge([ ], [X|Xs], [X|Xs]).
merge(Xs,[ ],Xs).
outro exemplo é o member definido em 3.12 que é redundante para queries do tipo:
member(a,[a,b,a,c]).
Definimos então uma versão não redundante:
member_check(X,Xs) <--
X é um membro da lista Xs
member_check(X,[X|Xs).
member_check(X,[Y|Ys]) <-- X ≠ Y, member_check(X,Xs).
Isto tem a ver como a maneira que ≠ é tratado no Prolog (cap.11).
ex2:
select(X,[X|Xs],Xs).
select(X,[Y|Ys,[Y|Ys]) <-- select(X,Ys,Zs).
este programa é similar ao member de 3.19. e não há problema com a ordem das
metas pois ele é minimal recursivo.
se select(X,[a,b,c],Xs) , dá 3 soluções para X e Xs
O programa só não termina se ambos os 2º e 3º argumentos forem lista incompletas.
Uma variante para este programa é se acrescentarmos o teste X≠Y à cláususla
recursiva. Como antes, assumimos que ≠ está apenas definido para argumento
ground. Somos conduzidos ao programa select_first, que tem um signifaicado
diferente de select, ao passo que member_check tinha um significado igual a member.
ex3:
permutation(Xs,[X|Ys]) <-- select(X,Xs,Zs), permutation(Zs,Ys).
permutation([ ], [ ]).
computações de permutation metas onde o primeiro argumento for uma lista completa,
terminam. Se for incompleta, porque o goal select não termina, é interminável.
Se a ordem das metas em permutation for trocada, é o 2º argumento que é
significativo para a terminação.
CAP.8 – ARITMÉTICA
Os programas lógicos para trabalhar a aritmética, dados na secção 3.1. são muito
elegantes mas não são práticos.
Isto porque não se pode ignorar os recursos que qualquer computador tem para as
operações aritméticas. Então temos de estender o Prolog puro para ter também
system predicates (evaluable predicates, builtin predicates, bips)
O que acontece se o termo a ser avaliado não é uma válida expressão aritmética?
Pode ser inválida por duas razões:
- Um termo como 3+x para uma constante x não pode ser avaliado. Em
contraste, um termo 3+Y para a variável Y pode ser ou não avaliado,
dependendo do valor de Y.
A semântica de um programa e, lógica é completaente definida e assim não pode
haver runtime errors. Por exemplo, a meta X is 3+Y tem as soluções {X=3, Y=0}.
Todavia quando interfaceamos com um computador temos de contar com as suas
limitações. Um runtime error acontece quando a computação não pode ser acabada
por falta de informações, isto é, variáveis não instanciadas. Isto é distinto das metas
que simplesmente falham.
Extensões do Prolog manejam com estes erros, suspendendo até que os valores das
variáveis em causa sejam conhecidos.
O modelo de Prolog introduzido não permite suspensões.
A query (X is 3+x) falha porque o argumento do lado direito não pode ser avaliado
como uma expressão aritmética.
Um erro comum dos iniciados é pensarem que is é o mesmo que assignar. Há a
tentação de escrever a meta, por exemplo, (N is N+1).
Outros predicados de sistema são <, ≤ (escrito =<), > e ≥ (escrito >=). O Prolog chama
directamente as operações aritméticas.
(A <B)?, A e B são avaliadas como expressões aritméticas. Os dois números
resultantes são comparados. ex: (N < 1)? gera um erro quando N é uma variável.
Testes de igualdade ou desigualdade devem usar os predicados: =:= e =/=.
Outra capacidade dos programas Prolog para aritmética é a estrutura recursiva dos
números. Nos programas lógicos, a estrutura é usada para determinar que regra
aplicar e para garantir terminação da computação.
Prog. 8.2.
factorial(N,F) <-
F é o inteiro N factorial
factorial(N,F) <--
N > 0, N1 is N-1, factorial(N1,F1), F is N*F1.
factorial(0,1).
factorial(Y,12)? dá erro
Prog. 8.4.
factorial(N,F) <-- factorial(N,1,F).
factorial(N,T,F) <--
N > 0, T1 is T*N, N1 is N-1, factorial(N1,T1,F).
factorial(0,F,F).
versão alternativa, marginalmente mais eficiente e de mais fácil leitura (menos
argumentos).
Prog 8.7.b
inner_product(Xs,Ys,IP) <-- inner_product(Xs,Ys,0,IP=.
inner_product([X|Xs],[Y|Ys],Temp,IP) <--
Temp1 is X*Y+Temp, inner_product(Xs,Ys,Temp1,IP).
inner_product([ ], [ ], IP,IP).
Prog. 8.10.
lenght(Xs,N) <--
lenght([X|Xs],N) <-- N > 0, N1 is N-1, lenght(Xs,N1).
lenght([ ],0).
Prog. 8.11.
lenght([X|Xs],N) <-- lenght(Xs,N1), N is N1+1.
lenght([ ],0).
Prog. 8.12.
range(M,N,[M|Ns]) <-- M < N, M1 is M+1, range(M1,N,Ns).
range(N,N,[N]).
CAP. 9 – Inspecção de Estruturas
São relações unárias que distinguem entre tipos diferentes de termos. O próprio
sistema já traz alguns e que dizem se um termo é uma estrutura ou uma constante, e
mais, se a constante é um átomo, um inteiro ou real (vírgula flutuante).
Os 4 predicados básicos do Prolog standard são:
integer(X) <-- X é um inteiro
atom(X) <-- X é um átomo
real(X) <-- X é um número em vírgula flutuante
compound(X) <-- X é um termo composto
Cada predicado básico pode ser visto como uma tabela infinita de factos.
Outros tipos de predicados podem ser construídos a partir dos básicos. Por exemplo,
para saber se um número é inteiro ou real:
number(X) <-- integer(X).
number(X) <-- real(X).
Também inclui atomic(X). que dá sim se for um átomo ou um número.
Neste livro chamamos-lhe antes constant. constant(X) <-- atomic(X).
atom(3)? falha; integer(X)? falha
Os únicos termos não abarcados são as variáveis. Isso será feito nos predicados
meta-lógicos do cap. seguinte.
Prog. 9.1.
flatten(Xs,Ys) <--
Ys é uma lista de elementos de Xs, que é uma lista em que os elemenetos
podem ser átomos ou listas.
flatten([X|Xs],Ys) <--
flatten(X,Ys1), flatten(Xs,Ys2), append(Ys1,Ys2,Ys).
flatten(X,[X]) <--
constant(X), X≠[ ].
flatten([ ], [ ]).
A condição constant(X) é necessária para prevenir que a regra seja usada quando X é
uma lista.
Este programa usando dupla recursão não é o mais eficiente. Pode requerer um nº de
reduções de ordem quadrática em relação ao número de membros.
Prog. 9.1.b
flatten(Xs,Ys) <--
Ys é uma lista dos elementos de Xs.
flatten(Xs,Ys) <-- flatten(Xs,[ ],Ys).
flatten([X|Xs],S,Ys) <--
list(X), flatten(X,[Xs|S],Ys).
flatten([X|Xs],S,[X|Ys]) <--
constant(X), X≠8 ], flatten(Xs,S,Ys).
flatten([ ],[X|S],Ys) <--
flatten(X,S,Ys).
flatten([ ], [ ], [ ]).
list([X|Xs]).
(o livro tem explicação por menorizada do funcionamento – pg.166)
As chamadas a um functor pode falhar por várias razões. Uma meta como
functor(father(X,Y),son,2) não unifica com um facto da tabela.
Também há restrições ao tipo dos argumentos a usar. p.ex: o 3º argumento de functor,
a aridade do termo, não pode ser atom ou termo composto.
Há ainda um outro problema de erro devido aos possíveis inumeras soluções:
functor(X,Y,2)?
Prog.9.3.
substitute(Old,New,OldTerm,NewTerm) <--
NewTerm é o resultado de substituir todas as ocorrências de Old em OldTerm
por New.
substitute(Old,New,Old,New).
substitute(Old,New,Term,Term) <--
constant(Term), Term≠Old.
substitute(Old,New,Term,Term1) <--
compound(Term),
functor(Term,F,N),
functor(Term1,F,N),
substitute(N,Old,New,term,Term1).
substitute(N,Old,New,Term,Term1) <--
N > 0,
arg(N,Term,arg,Arg1),
arg(N,Term1,Arg1),
N1 is N-1,
substitute(N1,Old,New,Term,Term1).
substitute(0,Old,New,Term,Term1).
Este tipo de predicados pode ser usado para dar alguma flexibilidade aos programas
que usam predicados de sistema e também para controlar a ordem das metas. É o
que veremos usando programas já estudados atrás.
Por ex: o programa plus que vamos ver abaixo pode ser usado para soma e
subtracção. A ideia é checar quais os argumentos que estão instanciados antes,
chamando o avaliador aritmético. Por exe. a 2ª regra diz que se o 1º e 3º argumentos,
X e Z, não são variáveis, o 2º argumento, Y, pode ser determinado como a sua
diferença. De notar que se os argumentos não forem inteiros isto falha.
Não dá erros mas ainda não pode ser usado para gerar partição de um número. Isso
requer geração de números.
Prog. 10.1.
plus(X,Y,Z) <--
A soma dos números X e Y é Z.
plus(X,Y,Z) <-- nonvar(X), nonvar(Y), Z is X+Y.
plus(X,Y,Z) <-- nonvar(X), nonvar(Z), Y is Z-Y.
plus(X,Y,Z) <-- nonvar(Y), nonvar(Z), X is Z-Y.
São precisos programas separados para encontrar o tamanho de uma lista ou para
gerar listas arbitrárias de comprimento N. Este programa usa testes meta-lógicos para
definir só uma relação. Ele evita a situação de não-terminação dos programas 8.10. e
8.11. caso ambos os argumentos não estivessem instanciados.
Os predicados meta-lógicos também podem ser usados para fazer a melhor escolha
da ordem das metas numa cláusula.
Prog.10.3.
grandparent(X,Z) <-- X é avô de Z.
grandparent(X,Z) <-- nonvar(X), parent(X,Y), parent(Y,Z).
grandparent(X,Z) <-- nonvar(Z), parent(Y,Z), parent(X,Y).
(procura + eficiente, se netos de um avô ou avô de um neto).
Prog.10.4.
ground(term) <-- Term é um termo ground.
ground(Term) <-- nonvar(Term), constant(term).
ground(Term) <--
nonvar(Term), ...para garantir que não gera erro...
compound(Term),
functor(Term,F,N),
ground(N,Term).
ground(N,Term) <--
N > 0,
arg(N,Term,Arg),
ground(Arg),
N1 is N-1,
ground(N1,Term).
ground(0,Term).
Prog.10.5.
unify(Term1,Term2) <-- Term1 e Term2 são unificados, ignorando o check de
ocorrência
unify(X,Y) <-- var(X), var(Y), X=Y.
unify(X,Y) <-- var(X), nonvar(Y), X=Y.
unify(X,Y <-- var(Y), nonvar(X), Y=X.
unify(X,Y) <-- nonvar(X), nonvar(Y), constant(X), constant(Y), X=Y.
unify(X,Y) <-- nonvar(X), nonvar(Y), compound(X), compound(Y),
term_unify(X,Y).
term_unify(X,Y) <-- functor(X,F,N), functor(Y,F,N), unify_args(N1,X,Y).
unify_args(N,X,Y) <-- N > 0, unify_arg(N,X,Y), N1 is N-1, unify_args(N1,X,Y).
unify_args(0,X,Y).
unify_arg(N,X,Y) <-- arg(N,X,ArgX), arg(N,Y,ArgY), unify(ArgX,ArgY).
Prog.10.5.
unify(Term1,Term2) <-- Term1 e Term2 são unificados, com o check de
ocorrência
unify(X,Y) <-- var(X), var(Y), X=Y.
unify(X,Y) <-- var(X), nonvar(Y), not_occurs_in(X,Y), X=Y.
unify(X,Y <-- var(Y), nonvar(X), not_occurs_in(Y,X), Y=X.
unify(X,Y) <-- nonvar(X), nonvar(Y), constant(X), constant(Y), X=Y.
unify(X,Y) <-- nonvar(X), nonvar(Y), compound(X), compound(Y),
term_unify(X,Y).
Prog.10.7.
occurs_in(Sub,Term) <-- Sub é um subtermo de Term.
a: usando ==
occurs_in(X,Term) <-- subterm(Sub,Term), X==Sub.
b: usando freeze
occurs_in(X,Term) <-- freeze(X,Xf), freeze(Term,Termf), subterm(Xf,Termf).
subterm(X,Term) <-- ver programa 9.2.
Freezing um termo faz uma cópia desse termo, Frozen, onde todas as variáveis não
instanciadas se tronam em constantes únicas. Um termo frozen parece, e pode ser
manipulado, como um termo ground. Frozen variáveis são vistas como átomos ground
durante as unificações. Duas variáveis frozen unificam se e só se são idênticas.
Similarmente, se um termo frozen e uma variável não instanciada forem unifficados,
eles tronam-se num termo idêntico frozen. O comportamento é o mesmo das
constantes. Por exemplo avaliação aritmética envolvendo variáveis frozen, falha.
Freezing dá-nos a capacidade de saber quando dois termos são idênticos. Dois
termos frozen, X e Y, unificam se e só se as suas versões unfrozen são idênticas, isto
é, X==Y. Esta propriedade é essencial ao correcto comportamento do prog.10.7.
melt é o contrário de frozen e qualquer instanciação de variável que tenha sido feita
enquanto esta estava frozen é tida em conta.
Versão alternativa de substitute, com não ground termos. O termo é frozen antes da
substituição para não haver instanciações acidentais.
non_ground_substitute(X,Y,Old,New) <-- freeze(Old,Old1), substitute(X,Y,Old1,Old2),
melt(Old2,New).
O termo frozen também pode ser usado como template para fazer cópias. O predicado
de sistema melt_new(Frozen,Term) faz uma cópia Term do termo frozen, onde as
variáveis frozen são substituídas por nopvas variáveis.
Um dos usos de melt_new é para copiar um termo:
copy(Term,Copy) <-- freeze(Term,Frozen), melt_new(Frozen,Copy).
O Prolog providencia o predicado copy_term(Term1,Term2).
Infelizmente freeze, melt e melt_new não estã presentes em implementações Prolog
existentes. São úteis para o cap.12 – predicados extra-lógicos.
O corte, denotado por !, pode ser usado para expressar a exclusividade mútua dos
testes. É colocado depois dos testes aritméticos. Por exemplo, a primeira cláusula da
função merge é escrita:
Outro ex: no mesmo tipo do merge. Calcular o mínimo. Uma vez que o teste aritmético
tem sucesso não há possibilidade dos outros teste o terem
minimum(X,Y,Min) <--
Min é o mínimo dos números X e Y
minimum(X,Y,X) <-- X ≤, !.
minimum(X,Y,Y) <-- X > Y, !.
Um exemplo mais substancial onde os cortes podem ser acrescentados para indicar
que o programa é determinista é dado pelo programa 3.29. O programa define a
relação polynomial(Term,X) para reconhecer se o Term é um polinómio em X
O resultado é um programa determinista que tem uma mistura de cortes depois das
condições e depois da unificação.
polynomial(Term, X) <--
Term é um polinómio em X.
polynomial(X,X) <-- !.
polynomial(Term,X) <--
constant(Term), !.
polynomial(Term1+term2,X) <--
!, polynomial(Term1,X), polynomial(Term2,X).
polynomial(Term1-Term2,X) <--
!, polynomial(Term1,X), polynomial(Term2,X).
polynomial(Term1*Term2,X) <--
!, polynomial(Term1,X), polynomial(Term2,X).
polynomial(Term1/Term2,X) <--
!, polynomial(Term1,X), constant(Term2).
polynomial(Term1↑N,X) <--
!, integer(N), N≥0, polynomial(Term,X).
Os (estes) green cuts não interferem com o significado do programa. Já o mesmo não
acontece com o red cuts que veremos à frente.
Os cortes interagem com os predicados de sistema tais como clall e ; introduzidos no
cap.10 e predicados com not que introduziremos mais à frente.
A questão é que alcance deve o cut ter. Isso vai para o cap.17. (não interessa).
Uma frase duplamente negada não é a mesma coisa que a afirmação equivalente. Isto
é como na linguagem natural.
O programa para verify pode ser usado em conjunto com o 10.8. para numbervars
para definir a noção de igualdade intermédia entre unificabilidade provida por =/2 e
igualdade sintáctica provida por ==/2. O predicado variants(X,Y) definido no programa
11.7 é verdade se os dois termos X e Y são variants.
Dois termos são variants se são instâncias um do outro (cap.4).
Isto pode ser conseguido com o seguinte truque, implementado no programa 11.7:
Instanciar as variáveis usando numbervars, testar se os termos unificam e desfazer a
instanciação.
As três formas de comparação =/2 variant/2 e ==/2 são progressivamente mais fortes,
com a unificabilidade sendo a mais fraca e mais geral.
Termos idênticos são variants e termos variantes são unificáveis. Para termos ground
todas as três comparações dão os mesmos resultados.
Prog. 12.1.
writeln([X|Xs]) <-- write(X), writeln(Xs).
writeln([ ]) <-- nl.
nl é o predicado construído, que causa que o próximo carácter de output seja escrito
numa nova linha.
ex: executar o goal conjuntivo
(X=3, writeln([‘The value of X is ‘, X]) produz o output The value of X is 3.
O read e o write operam ao nível do termo. Um nível mais baixo é o do carácter, que é
assumido na maioria das implementações do Prolog como ASCII.
O predicado básico de output é:
put_char(Char).
O Prolog standard permite especificar o stream de output mas não vamos dar.
de entrada é o get_char(Char).
vamos ver dois exemplos de programas que usam o ciclo básico de leitura de um
termo, e depois processam-no. O primeiro é um editor de linha. O segundo é um
programa interactivo que é uma shell para comandos Prolog, que é essencialmente
um interpretador de nível de topo para Prolog, em Prolog.