






Estude fácil! Tem muito documento disponível na Docsity
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Prepare-se para as provas
Estude fácil! Tem muito documento disponível na Docsity
Prepare-se para as provas com trabalhos de outros alunos como você, aqui na Docsity
Encontra documentos específicos para os exames da tua universidade
Prepare-se com as videoaulas e exercícios resolvidos criados a partir da grade da sua Universidade
Responda perguntas de provas passadas e avalie sua preparação.
Ganhe pontos para baixar
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Na linguagem de programação Prolog, existem essencialmente duas maneiras de ... Quando o procedimento p é executado, o Prolog avalia o objetivo member(X, ...
Tipologia: Notas de estudo
1 / 11
Esta página não é visível na pré-visualização
Não perca as partes importantes!







NOTA: Estas notas são insuficientes em diversos aspetos. São notas avulsas. As explicações e os exemplos são também insuficientes. Elas resultam essencialmente de respostas dadas a pedidos de ajuda que nos fizeram chegar por email. Não servem, por isso, como material suficiente para se estudar a matéria. O livro “Programming in Prolog”, de Clocksin & Mellish, é a fonte de consulta aconselhada para toda a linguagem Prolog.
1 Mecanismos de repetição em Prolog ..................................................................................... 3
2 Efeito do fail na repetição ..................................................................................................... 3
3 E o repeat? ............................................................................................................................ 4
4 É fácil cometer erros quando se usam cuts ............................................................................ 6
5 Compilação de Erros Frequentes no Prolog Procedimental .................................................. 8
5.1 Repetir um processo que não tem soluções alternativas ............................................... 8
5.2 Repetir um processo que tem soluções alternativas .................................................... 10
O objetivo member(X, [a, b, c]) tem outra solução (X=b). Com esta solução, o Prolog vai agora avançar e tentar não falhar desta vez. Escreve o b e muda de linha. Como a cláusula da definição tem o fail , ele volta a falhar, volta a retroceder até chegar de novo ao member(X, [a, b, c]) , o qual tem mais uma solução (X=c).
O Prolog volta a imprimir o X (X=c), volta a mudar de linha, volta a falhar, volta a retroceder até chagar de novo a member(X, [a, b, c]).
Desta vez, member(X, [a, b, c]) já não tem soluções alternativas. Portanto, como não pode retroceder mais, o Prolog falha (apesar de ter feito tudo quanto devia). É essa a razão da mensagem False que o interpretador escreve no final da execução.
Para evitar esta falha, que não deve ocorrer, acrescenta-se uma nova cláusula do procedimento p , a qual serve apenas para não falhar:
p :- member(X, [a, b, c]), write(X), nl, fail. p.
Agora, após esgotar as alternativas da cláusula 1, o Prolog passa para a cláusula 2. Esta não origina nenhuma espécie de computação, mas tem sucesso.
Mas há mais coisas a saber sobre este assunto, nomeadamente a interação do fail com o cut , e a necessidade de usar o repeat em certos programas.
A pergunta que vos deixo é: será que a seguinte solução funciona?
p :- member(X, [a, b, c]), write(X), nl, !, fail. p.
Porquê?
Imagina que queres escrever a palavra Olá repetidas vezes. O fail não funciona:
?- write(ola), nl fail. ola false ?-
O Prolog começa por escrever olá, depois muda de linha, depois falha. Quando falha, retrocede para o nl , procurando uma solução alternativa. O nl não tem soluções alternativas – não se pode mudar de linha de maneiras diferentes. Como o nl não tem soluções alternativas, o Prolog retrocede para o passo anterior, o write(ola) , o qual também não tem soluções alternativas (não se pode escrever olá de várias maneiras). Não tendo mais para onde retroceder, o Prolog falha mesmo (daí a mensagem false que o interpretador escreve no fim).
Conclusão: o fail não originou uma repetição porque aquilo que pretendemos repetir não tem soluções alternativas. Quando isso acontece, temos de usar o repeat , mas ….
O repeat não repete coisa nenhuma:
?- repeat, write(ola), nl. ola true
O repeat é um predicado que tem sempre soluções alternativas, o que só por si, não origina nenhum repetição.
Para que o Prolog possa repetir seja o que for, sem recorrer à recursividade, é necessário conjugar a geração de soluções alternativas com a falha. Quando o Prolog falha, retrocede até ao passo anterior àquele em que falhou, e tenta encontrar uma solução alternativa nesse passo que lhe permita não falhar no passo seguinte. Dado que o repeat tem sempre soluções alternativas, pode combinar-se o repeat com o fail para originar uma repetição.
O que aconteceria na seguinte interação?
?- repeat, write(ola), nl, fail.
O Prolog repetiria indefinidamente a escrita do átomo olá.
Já arranjámos uma maneira de repetir algo que não tem soluções alternativas (escrever olá), combinando as indefinidas soluções alternativas do repeat com a falha do fail. Depois de escrever olá e mudar de linha, o Prolog falha devido ao fail. Retrocede até ao nl , mas não encontra soluções alternativas. Volta a retroceder e volta a não encontrar soluções alternativas. Finalmente, retrocede até ao repeat , o qual tem sempre mais uma solução. Com essa nova solução, o Prolog avança procurando não falhar: escreve olá , muda de linha, e falha. Volta a retroceder até ao repeat , volta a avançar, volta a escrever olá , volta a falhar, volta a retroceder… e assim indefinidamente.
Isto é quase o que pretendemos: repetir um processo que originalmente não tinha soluções alternativas. Mas queremos que esta repetição termine; não queremos que ela prossiga indefinidamente.
Como conseguir parar a repetição? Como o fail falha sempre, e como é a falha que obriga o Prolog a retroceder até encontrar o repeat , em vez do fail , temos de usar algo que falhe enquanto pretendermos repetir, e que não falhe, quando já não pretendermos repetir. Supondo que o predicado cond falha em certas condições e tem sucesso nas outras condições, o seguinte código faria o que pretendemos:
?- repeat, write(ola), nl, cond.
Isto repetiria até que a condição cond fosse verdade. Se cond falhar, tudo se passa como quando usámos o fail. Porém, assim que cond tiver sucesso, o Prolog não falha (não tendo por isso que retroceder em busca de soluções alternativas que lhe permitissem não falhar). O predicado cond não existe. Em cada caso, temos de implementar algo que funcione dessa maneira. Esta é a base de todos os programas que repetem um processo, combinando o repeat com a falha.
O seguinte programa gera um inteiro entre 1 e 5 e depois implementa um ciclo em que pede ao utilizador para adivinhar o número gerado. Se o utilizador adivinhar, o ciclo termina. Se o utilizador não adivinhar, o programa volta a pedir-lhe a sua nova tentativa. Isto repete-se até que o utilizador acerta.
Esta definição funciona perfeitamente para determinar o maior de dois números:
?- maior(2, 8, X). X = 8 ?- maior(10, 5, X). X = 10
O predicado funciona bem se o terceiro argumento for uma variável não instanciada. Se for uma constante, o predicado poderá comportar-se erradamente:
?- maior(7, 2, 7). % Funciona bem True
?- maior(7, 2, 1). % Funciona bem False
?- maior(7, 2, 2). % ERRADO. Funciona mal True
Voltemos à definição para perceber a causa do mau funcionamento:
maior(X, Y, X):- X >= Y, !. maior(_, Y, Y).
Na chamada maior(7, 2, 2), a primeira cláusula não pode ser usada porque a cabeça da cláusula, maior(X, Y, X), não emparelha com o objetivo, maior(7, 2, 2). Portanto, a execução não chega ao cut. Assim, o Prolog pode recorrer à segunda cláusula. Como a cabeça da segunda cláusula, maior(_, Y, Y), emparelha com o objetivo, maior(7, 2, 2), o Prolog tem sucesso. Mas deveria ter falhado, pois o maior de 7 e 2 é 7.
Como se corrige?
Como o problema surge apenas quando o predicado é chamado com uma constante no terceiro argumento, a solução consiste em chamar o predicado com uma variável (já vimos que funciona bem) e, depois, verifica-se se o valor devolvido emparelha com o argumento usado na chamada. A nova versão do predicado chama-se maximum/3 :
maximum(X, Y, M):- maior(X, Y, Z), M = Z. % Z é uma variável sem valor
maior(X, Y, X):- X >= Y, !. maior(_, Y, Y).
Outra solução
Na literatura é frequente encontrar solução diferente:
maior(X, Y, X):- X >= Y, !. maior(_, Y, Y):- X < Y.
Mas então o cut não deveria servir para evitar computações repetidas? A segunda cláusula volta a comparar as variáveis X e Y, apesar de a primeira cláusula já o ter feito. Então que vantagens tem este cut?
Em termos de eficiência, apenas se evita que a segunda cláusula seja usada se a primeira tiver tido sucesso. Se X for maior que Y, a execução do programa passa pelo cut , comprometendo-se
com a primeira cláusula. No entanto, se X for menor que Y, a execução passa para a segunda cláusula voltando a comparar X com Y. Os ganhos de eficiência são menores
Mas o programa ganha em legibilidade. Esta solução com o cut tem exatamente a mesma leitura que a solução sem o cut. Ou seja, a introdução deste tipo de cut não altera a leitura do programa, em relação à definição puramente declarativa. O cut usado sem altear a leitura lógica do programa em relação à definição declarativa pura chama-se green cut.
A utilização de green cuts consiste num bom compromisso entre a legibilidade do código e a eficiência. Quando se remove um green cut , o programa continua a funcionar (só que envolvendo mais computação).
Os cuts cuja remoção resultar em programas que deixam de funcionar chamam-se red cuts. Há situações em que é necessário usar red cuts , mas eles deve ser usados apenas se estritamente necessário.
As duas secções seguintes analisam erros encontrados em provas de avaliação de alunos de Inteligência Artificial. A análise tem por objetivo chamar a atenção dos alunos para erros a evitar.
Nesta secção apresentam-se exemplos de dois erros frequentes em procedimentos que repetem processos computacionais que não têm soluções alternativas, recorrendo à repetição por falha. Como aquilo que se pretende repetir não tem soluções alternativas, é necessário usar o predicado repeat/0 , o qual gera um número indefinido de soluções alternativas. Em geral, qualquer solução que não use o repeat/0 ou que não tenha uma falha está totalmente errada.
O procedimento que serve de enquadramento aos erros que se exemplificam serve para imprimir, no monitor do computador, os números de contribuinte de pessoas cujo nome é inserido, pelo utilizador, através do teclado do computador. O ciclo implementado pelo programa termina quando o utilizador introduzir o átomo fim.
Exemplo de interação com o programa, e que o utilizador introduz sucessivamente nomes de pessoas e o programa imprime os seus NIFs, até que o utilizador introduz o átomo fim :
?- nifs. Introduz um nome : mariana. 123456789 Introduz um nome : catarina. 345678912 Introduz um nome : fim. True.
Na implementação do procedimento nifs/0 , partimos do princípio que existe uma base de dados com os NIFs de todas as pessoas, como nos seguintes exemplos:
nif(mariana, 123456789). nif(ze, 234567891). nif(catarina, 345678912). O predicado de leitura dos nomes das pessoas e o corpo principal do procedimento nifs/0 são os seguintes:
Esta resolução tem um erro mais subtil, mas também muito grave. Quando o procedimento processar_pessoa/1 recebe um nome (diferente de fim ), ele deve obter o NIF da pessoa (através do predicado nif/2 ), deve imprimir o NIF obtido, e deve falhar.
De facto, a primeira cláusula obtém corretamente o NIF da pessoa recebida, imprime corretamente o NIF, e falha. Tudo parece bem, mas não está. Ao falhar, o Prolog retrocede em busca de uma solução que não falhe. As ações nl/0 e write/1 não têm soluções alternativas, e o predicado nif/2 também não (cada pessoa tem apenas um NIF). Isto significa que esta primeira cláusula se esgota (é incapaz de fornecer uma solução para o problema). Assim sendo, o Prolog passa para a segunda cláusula, a qual não falha. Consequentemente, como o procedimento processar_pessoa/1 não falha, o procedimento nifs/0 não repete. Mas nós pretendíamos que ele repetisse.
Nesta secção apresenta-se exemplo de um erro num procedimento que repete um processo computacional enquanto este tiver soluções alternativas, recorrendo à repetição por falha. Como aquilo que se pretende repetir tem soluções alternativas, não se pode usar o predicado repeat/. Em geral, qualquer solução que use o repeat/0 ou que não tenha uma falha está totalmente errada.
O procedimento que serve de enquadramento ao erro exemplificado serve para imprimir, no monitor do computador, os identificadores e os preços dos produtos de uma dada classe existentes numa loja. O ciclo implementado pelo programa termina quando não existirem na loja mais produtos da classe especificada.
Na implementação do programa, parte-se do princípio que cada produto, a classe a que pertence e o seu preço é mantido numa base de dados constituída por factos do predicado product/3, como os seguintes exemplos:
product(cup1, cup, 10). product(cup2, cup, 5). product(p1, plate, 7). product(g1, glass, 3).
Exemplo de funcionamento do procedimento pretendido:
?- print_products(cup). cup1: 10 cup2: 5 true
O procedimento print_products/1 é um ciclo que repete a consulta de um produto da classe especificada ( cup , no exemplo), a impressão do identificador e do preço do produto obtido da base de dados, enquanto houver produtos da classe especificada.
A implementação correta do procedimento print_products/1 é a seguinte:
print_products(Class):- product(Prod, Class, Price), write(Prod), write(‘: ‘), write(Price), nl, fail. print_products(_).
A primeira cláusula da definição de print_products/1 efetua todo o trabalho mas falha quando já não há produtos da classe especificada. Nessa altura, a execução do procedimento passa para a segunda cláusula e o programa termina com sucesso. Resoluções que não tenham a segunda cláusula estão erradas porque o predicado, ainda que fizesse o que se pretende, falharia. Resoluções que não tenham a falha no final da primeira cláusula estão erradas porque não haveria repetição.
Apresenta-se seguidamente um erro grave e uma sua variação igualmente grave.
print_produto(Class):- assert(produto(Produto, Class, Preco)), write(Produto:Preco), nl, fail. print_produto(_).
A definição da primeira cláusula segue um padrão semelhante ao padrão correto mas apresenta um erro absurdo que reflete mais do que uma concessão errada: assert(produto(Produto, Class, Preco)).
print_produto(Class):- retract(produto(Produto, Class, Preco)), write(Produto:Preco), nl, fail. print_produto(_).
Este programa funcionaria, mas apagaria a base de dados com os produtos. Portanto, seria um erro também muito grave. De facto a ação retract/1 instancia as variáveis do seu argumento com os valores encontrados no facto removido, por isso, o programa consegue imprimir o identificador do produto e o seu preço, de acordo com o desejado. Mas a ação retract/1 remove os factos que emparelham com o seu argumento, o que é totalmente inaceitável.