


























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
INTRODUÇÃO À PROGRAMAÇÃO EM LÓGICA
Tipologia: Notas de estudo
1 / 34
Esta página não é visível na pré-visualização
Não perca as partes importantes!



























Universidade Federal de Campina Grande - UFCG Centro de Ciências e Tecnologia - CCT Departamento de Sistemas e Computação - DSC
Alexandre de Andrade Barbosa [email protected]
Joseluce de Farias Cunha [email protected]
34 páginas
Av. Aprígio Veloso, 882 Bodocongó Caixa Postal 10. 58.109-970 Campina Grande PB Brasil Fone: 310-1119 Fax: 310-
Alexandre de Andrade Barbosa Departamento de Sistemas e Computação E-mail: [email protected]
Joseluce de Farias Cunha Departamento de Sistemas e Computação E-mail: [email protected]
Av. Aprígio Veloso, 882 Bodocongó Caixa Postal 10. CEP 58109-970 Campina Grande PB Brasil Fone: 310-1119 Fax: 310-1273 (DSC)
Resumo Esta apostila foi criada como material de apoio à disciplina de Lógica Matemática do curso de Ciência da Computação da Universidade Federal de Campina Grande - UFCG. O conteúdo apresentado neste material é relacionado à Programação em Lógica, mais especicamente à linguagem Prolog. Nenhuma revisão sobre Lógica Matemática é apresentada, assim, é necessário que o leitor já possua conhecimento sobre Lógica Proposi- cional e Lógica de 1a^ Ordem. Este texto possuí nível introdutório, uma vez que conhecimentos avançados sobre a linguagem não serão explorados na disciplina em questão.
Palavras-chave: Programação em lógica, Prolog, Lógica.
Esta apostila foi criada para apoiar a disciplina Lógica Matemática, em sua parte nal relacionada à Programação em Lógica. O conteúdo apresentado aqui possui nível in- trodutório, uma vez que a disciplina em questão não fará uso de recursos avançados da linguagem. Estudos mais avançados sobre a Programação em Lógica deverão realizados em disciplinas posteriores. O conteúdo deste texto busca fornecer aspectos mais práticos relacionados à linguagem Prolog, o funcionamento interno da linguagem não será detalhado neste material. Caso o leitor deseje se aprofundar no estudo da linguagem, recomenda-se a leitura de [1], [2], [3] e [4], nos quais este texto é baseado. Espera-se que após a leitura deste material o leitor seja capaz de:
Existem diversos paradigmas de programação, hoje, o paradigma mais utilizado é o paradigma procedural. As linguagens Java, Pascal, C/C++ são ditas linguagens pro- cedurais, pois, nestes programas é necessário implementar um procedimento para resolver determinado problema. Outro paradigma de programação é o paradigma imperativo, que possui a linguagem LISP como sua representante mais famosa. A Programação em Lóg- ica faz parte do paradigma de programação denominado declarativo ou descritivo, neste, deve-se implementar uma descrição do problema e não um conjunto de instruções. A linguagem Prolog é uma representante do paradigma declarativo, esta é a represen- tante mais famosa da Programação em Lógica, a qual se baseia no cálculo de predicados. Prolog foi criada em 1972 por Colmerauer e Roussel, um programa Prolog não possui código para manipular a memória ou realizar desvios condicionais. Isso não signica que Prolog seja superior às outras linguagens, pode-se armar apenas que a linguagem é mais adequada para solucionar uma determinada categoria de problemas. Esta categoria diz respeito aos problemas onde é necessário representar algum tipo de conhecimento, por ex- emplo, em aplicações que realizem computação simbólica, na compreensão de linguagem natural ou em sistemas especialistas.
Um programa Prolog constitui-se de uma coleção de fatos (base de dados) e regras (re- lações lógicas), esses ítens descrevem o domínio de um determinado problema. Esta descrição do problema é avaliada por um interpretador, o qual utilizando um motor de inferência realiza deduções em busca de conclusões válidas para consultas realizadas pe- los usuários. Assim, pode-se armar que a computação destes programas é equivalente a prova de um teorema em lógica.
existem versões livres e comerciais criadas para os principais sistemas operacionais. Al- gumas das implementações mais conhecidas são:
Outras distribuições Prolog conhecidas são: GNU Prolog [13], XSB [14] e Trinc Prolog [15]. A implementação utilizada ao longo desta apostila é a SWI-Prolog, todo código utilizado nos exemplos e exercícios apresentados aqui foram desenvolvidos nesta versão de Prolog.
Assim como todas as linguagens de programação, existem algumas regras que devem ser respeitadas pelo programador. Os dados representados em Prolog podem ser um dos seguintes tipos:
atômicas; ∗ átomos - são constantes expressas através de palavras. Devem ser iniciados com letra minúscula seguida de qualquer caractere alfanumérico. Caso seja necessário denir um átomo com letra maiúscula ou número, deve se usar aspas simples. Ex.: joao, João',16', `Marys', ...; ∗ inteiros - qualquer número que não contenha um ponto (.) será considerado um inteiro. Caracteres ASCII entre aspas duplas são considerados inteiros. Ex.: 1, 6, -3, a (interpretado como 97), ...; ∗ números em ponto utuante - qualquer número com um ponto e pelo menos uma casa decimal. Ex.: 5.3 (correto), 7.8 (correto), 7. (incorreto); não atômicas; ∗ listas - é uma seqüencia de elementos ordenados. Uma lista é declarada entre colchetes e os elementos devem ser separados por vírgula. Pode-se separar a cabeça (1o. elemento) do corpo (demais elementos) de uma lista utilizando |. Ex.: [ a, b, c], [a | b, c ], ....
O comando write exibe o valor do parâmetro no dispositivo de saída corrente. O dis- positivo padrão é o monitor, assim, o comando write(`Teste de impressão.'). irá exibir a mensagem Teste de impressão. na tela do monitor. O mesmo comando pode ser utilizado para imprimir o valor de qualquer variável. No entanto, não existe um comando padrão Prolog para escrita de expressões for- matadas. Devido a isso, o SWI-Prolog utiliza comandos de extensão, um deste é o comando writef do C-Prolog de Edinburgo. Este comando possui a seguinte sintaxe writef(Formato, Argumentos). Onde as opções para determinar a formatação são:
Após realizar o download e a instalação do SWI-Prolog e do SWI-Prolog-Editor, inicie o editor, será apresentada a tela deste, a qual deve ser similar a exibida na Figura 1. Pode-se observar que a tela se divide em duas janelas editáveis, identicadas na imagem com os números 1 e 2. A área 1 corresponde a janela de edição do programa, a área 2 é a janela de interação do usuário com o interpretador. Para que se possa realizar qualquer interação com o programa que está sendo editado é necessário, após salvar o arquivo, clicar sobre botão de consulta, identicado com o número 3. Caso o arquivo seja alterado e o botão não seja acionado o interpretador irá trabalhar com a última versão do programa que foi consultada. O número 4 identica o prompt de comandos, a partir deste é que podem ser enviados os comandos para o interpretador. Todo comando enviado para o interpretador deve obrigatoriamente ser nalizado pelo caractere ponto (.). Por exemplo, o comando ?- write(Teste) não resultará em uma impressão, enquanto o comando ?- write(Teste). resultará na impressão da string `Teste'. O símbolo ?- será usado nos exemplos e exercícios apresentados para representar o prompt de comandos, ele não deve ser digitado. As outras funcionalidades (e.g. salvar, abrir, debug, ... ) existentes no sistema são similares ou idênticas à ações encontradas em diversos sistemas. Maiores detalhes de funcionamento do programa podem ser encontradas no manual do sistema.
Figura 1: Tela inicial do SWI-Prolog Editor
Como já foi armado, um programa Prolog captura a descrição de um determinado prob- lema. O programador deve implementar um conjunto de fatos e regras relacionados ao problema em questão. Ao longo de toda esta seção serão apresentados alguns fatos, regras e consultas Prolog que podem constituir um programa. Para facilitar a explicação destes conceitos será implementado um programa que descreva as relações de uma família. A Figura 2 exibe a árvore genealógica de uma família, nesta é possível observar que existem diversos tipos de relação, por exemplo, bob é pai de pat, pam é avó de ann, ann é irmã de pat, jim é lho de pat, entre outras.
Figura 2: Árvore genealógica
Para se representar estas relações em Prolog, inicialmente pode-se criar a relação genitor(x, y), signicando que x é genitor de y. Então, podemos inserir na janela de edição do programa os seguintes fatos:
g e n i t o r (pam , bob ). g e n i t o r ( tom , bob ). g e n i t o r ( tom , l i z ). g e n i t o r ( bob , ann ). g e n i t o r ( bob , pat ). g e n i t o r ( pat , jim ).
Estas cláusulas descrevem toda a informação sobre a relação genitor existente na árvore apresentada (domínio). Após a denição desta relação podem ser realizadas consultas no sistema, para isso basta clicar no botão de consulta, e digitar perguntas no prompt de comandos, tal como exibido a seguir:
3 ?− g e n i t o r ( pat , jim ). Yes 4 ?− g e n i t o r ( jim , pat ). No
a consulta rotulada com o número 3, retorna a resposta yes (sim), isso ocorre pois o motor de inferência encontrar o fato correspondente e responder positivamente. Já para
cláusulas separadas por vírgula só sera satisfeita se e somente se todas as cláusulas forem satisfeitas. Do mesmo modo, pode-se armar que uma seqüencia de cláusulas separadas por ponto e vírgula sera satisfeita se ao menos uma cláusula for satisfeita. Um outro conectivo importante é a negação. Para exemplicar a utilização deste conec- tivo serão denidos os predicados homem(x) e mulher(x), signicando que x é homem e x é mulher respectivamente. Assim, são denidos os fatos: mulher (pam ). homem( tom ). homem( bob ). mulher ( l i z ). mulher ( pat ). mulher ( ann ). homem( jim ).
após denidos os fatos, podemos indagar, por exemplo, quem é a mãe de bob, ou seja, deseja-se saber quem é o genitor de bob que é mulher. Para isso, pode-se digitar no prompt de comandos a seguinte pergunta: 9 ?− g e n i t o r (X, bob ) , mulher (X ). X = pam ; No
a mesma consulta poderia ser realizada através da seguintes pergunta: 10 ?− g e n i t o r (X, bob ) , not (homem(X ) ). X = pam ; No
Muitas outras consultas podem ser realizadas sobre uma base de fatos, porém, é muito mais interessante utilizar regras, pois o poder de expressão obtido é muito maior. Para exemplicar o uso de regras será denida a relação prole(y,x), signicando que y é prole de x. Esta relação é a relação inversa de genitor(x,y), assim, pode-se armar que y é prole de x se x é genitor de y. Para isso, pode-se criar a seguinte regra na janela de edição do programa: p r o l e (Y,X) :− g e n i t o r (X,Y ).
o símbolo :- pode ser lido como se. A parte da regra a esquerda do símbolo :- é denom- inada de conclusão (ou cabeça), já a parte a direita deste é chamada de condição (ou corpo). Assim, para responder a consulta o interpretador Prolog precisa satisfazer parte condicional da regra, uma vez que não existem fatos relacionados a prole, para então obter uma conclusão. Para isso, são utilizadas substituições, até que se satisfaça a parte condicional ou não existam mais possibilidades de substituição. Consultas são realizadas sobre regras da mesma maneira como se estas fossem fatos, por exemplo, para indagar quem é a prole do indivíduo tom deve-se digitar no prompt de comandos a seguinte consulta: 11 ?− p r o l e (X, tom ).
X = bob ; X = l i z ; No
para satisfazer a regra o interpretador Prolog substituiu a variável X até que encontrou o fato genitor(tom, bob), o qual corresponde a parte condicional da regra prole(Y,X) :- genitor(X,Y), após as substituições adequadas. Posteriormente foi encontrado o fato genitor(tom, liz), o qual também pode satisfazer a regra, nenhuma outra substituição resultou em sucesso. Outras relações podem ser denidas para o exemplo. Pode-se denir as relações mae(x,y) e avos(x,y), apresentadas anteriormente como consultas. Para isso, deve-se digitar na janela de edição do programa as seguintes regras: mae (X,Y) :− g e n i t o r (X,Y) , mulher (X ). avos (X, Z) :− g e n i t o r (X,Y) , g e n i t o r (Y, Z ).
Um outra relação que pode ser denida é a relação irma(x,y), signicando x é irmã de y. Note que deve-se ter cuidado na denição deste relacionamento, pois, pode-se denir este através da seguinte regra: irma (X,Y) :− g e n i t o r (Z ,X) , g e n i t o r (Z ,Y) , mulher (X ).
porém, esta regra permite uma pessoa seja irmã de si mesma, para comprovar isso basta realizar a seguinte consulta: 12 ?− irma ( pat , pat ). Yes
o programador deve estar atento a especicações deste tipo, para descrever corretamente a relação é necessário indicar que x e y precisam ser diferentes, assim a regra correta seria: irma (X,Y) :− g e n i t o r (Z ,X) , g e n i t o r (Z ,Y) , mulher (X) , not (X = Y ).
Uma diferença básica entre uma regra e um fato é que um fato é sempre uma in- formação verdadeira, já uma regra precisa ser avaliada para que se possa determinar se esta é verdadeira ou não. Regras podem depender diretamente de um fato, como no ex- emplo anterior ou de outras regras (inclusive dela mesma). Regras denidas em termos de si mesma são chamadas de regras recursivas ou recorrentes, este tipo de regra será apresentado na seção seguinte.
A recursão é um dos elementos mais importantes da linguagem Prolog, este conceito permite a resolução de problemas signicativamente complexos de maneira relativamente simples. A construção de uma regra recursiva será apresentada através da denição da relação descendente(x,y), signicando que y é um descendente de x, tal como ilustrado na Figura 3. É possível denir esta relação utilizando a relação genitor, assim, uma descendência direta, ou seja, quando x é genitor de y, seria representada com a seguinte regra: d e s c e n d e n t e (X, Z) :− g e n i t o r (X, Z ).
O programa completo que descreve as relações familiares discutidas nesta seção pode ser observado a seguir:
/∗ Programa P r o l o g s o b r e r e l a ç õ e s f a m i l i a r e s. ∗/ g e n i t o r ( pam , bob ). % f a t o g e n i t o r ( tom , bob ). % f a t o g e n i t o r ( tom , l i z ). % f a t o g e n i t o r ( bob , ann ). % f a t o g e n i t o r ( bob , pat ). % f a t o g e n i t o r ( pat , jim ). % f a t o mulher (pam ). % f a t o mulher ( l i z ). % f a t o mulher ( pat ). % f a t o mulher ( ann ). % f a t o homem( tom ). % f a t o homem( bob ). % f a t o homem( jim ). % f a t o p r o l e (Y,X) :− g e n i t o r (X,Y ). % r e g r a mae (X,Y) :− g e n i t o r (X,Y) , mulher (X ). % r e g r a avos (X, Z) :− g e n i t o r (X,Y) , g e n i t o r (Y, Z ). % r e g r a irma (X,Y) :− g e n i t o r (Z ,X) , g e n i t o r (Z ,Y) , mulher (X ). % r e g r a d e s c e n d e n t e (X, Z) :− g e n i t o r (X, Z ). % r e g r a d e s c e n d e n t e (X, Z) :− g e n i t o r (X,Y) , d e s c e n d e n t e (Y, Z ). % r e g r a r e c u r s i v a
Um conjunto de regras utilizados para descrever uma única relação é, em geral, chamada de procedimento (procedure). Assim, as regras relacionadas à descendência, ilustradas no exemplo, podem ser denominadas de procedimento.
Uma consulta Prolog é sempre um conjunto de metas. Quando uma pergunta é feita, Prolog precisa satisfazer todas as metas. Isso, como já armado, é equivalente a provar um teorema ou realizar uma dedução. Para isso, Prolog busca vericar se a(s) meta(s) são conseqüências lógicas dos fatos e regras contidos no programa. Para ilustrar o pro- cedimento executado para responder uma consulta será utilizado o programa de exemplo, relacionado à relacionamentos familiares. Diga-se que a consulta ?- descendente(tom, pat). seja realizada. Sabe-se que descen- dente(bob, pat) é um fato existente no programa. Este fato seria derivado a partir da primeira regra relacionada a descendência. Além disso, sabe-se que a regra genitor(tom, bob) é um fato. Com isso, e o fato derivado descendente(bob, pat) pode-se concluir de- scendente(tom, pat) utilizando a segunda regra relacionada à descendência existente no programa. Isso ilustra o que foi utilizado pra realizar a prova, será apresentado agora como esta prova foi obtida. Prolog encontra uma prova na ordem inversa a forma ilustrada anteriormente. Prolog inicia com uma meta e, utilizando regras, substitui estas metas por novas metas, até que uma meta seja satisfeita por um fato. Assim, feita a pergunta descendente(tom, pat), Prolog procede da seguinte maneira. Para satisfazer esta meta é procurada alguma cláusula (fato ou regra) da qual a meta possa ser deduzida. Então, são encontradas as duas regras relacionadas à descendência existentes no programa, pois, a cabeça da regra corresponde à meta. Como a regra de-
scendente(X,Z) :- genitor(X,Z). aparece primeiro, e como a meta atual é descendente(tom, pat), as variáveis são substituídas, tal como a seguir:
X = tom, Z = pat
a meta descendente(tom, pat) é substituída pela meta genitor(tom, pat). Como não existe uma cláusula onde a cabeça seja correspondente a esta, Prolog realiza um encaminhamento para trás, ou seja, retorna a meta original para tentar encontrar uma maneira alternativa de satisfazê-la. Então, a regra descendente(X,Z) :- genitor(X,Y), descendente(Y,Z). será utilizada. Mais uma vez, as variáveis X e Z serão substituídas por tom e pat respectivamente, porém Y ainda não foi substituída. Assim, a meta atual dá lugar as metas genitor(tom,Y), descendente(Y, pat). Prolog deve agora satisfazer a conjunção de metas genitor(tom,Y), descendente(Y, pat), isso é feito na ordem em que as metas estão escritas, ou seja, primeiramente Prolog irá tentar satisfazer genitor(tom,Y). Realizando uma busca por cláusulas que satisfaçam a meta, Prolog encontra o fato genitor(tom, bob), assim, Y é substituída por bob. Com isso, a meta atual é descendente(bob, pat). Para satisfazer esta meta a regra descendente(X,Z) :- genitor(X,Z) será usada nova- mente. Observe que esta é uma nova seqüencia de prova, sendo assim, as substituições anteriores não possuem nenhuma relação com esta. Com isso, Prolog usa um novo con- junto de variáveis, e assim, pode-se reescrever a regra como descendente(X1,Z1) :- geni- tor(X1,Z1). Assim, como a cabeça deve corresponder meta, as seguintes substituições são realizadas:
X = bob, Z = pat
e então, a meta atual é trocada para nova meta genitor(bob, pat). Como esta é satisfeita por um fato presente no programa o procedimento acaba.
Listas são um dos tipos de dados mais úteis existentes na linguagem Prolog, diz-se que uma lista é uma seqüência ordenada de uma quantidade qualquer de elementos. Os elementos de uma lista podem ser de qualquer tipo, tais como, números ou átomos. Os elementos contidos em uma lista devem ser separados por vírgulas, e precisam estar entre colchetes. Por exemplo, uma lista pode conter os nomes dos indivíduos do exemplo da seção anterior, esta lista seria denida como:
[pam, liz, pat, ann, tom, bob, jim]
Existem dois tipos de listas, as listas vazias e as não vazias. Uma lista vazia é repre- sentada por [ ]. Listas não vazias podem ser divididas em duas partes, são elas:
Por exemplo, para a lista:
conc([a,b], [], [a,b]) = true conc([a,b], [c,d], [a,b,c,d]) = true
Para denir esta relação é pode-se implementar as seguintes regras: conc ( [ ] , L , L ). conc ( [X| L1 ] , L2 , [X| L3 ] ) :− conc ( L1 , L2 , L3 ).
Denidas as regras, pode-se realizar as seguintes consultas: 6 ?− conc ( [ a , b ] , [ c ] , L ). L = [ a , b , c ] ; No 7 ?− conc ( [ a ] , [ b , c ] , L ). L = [ a , b , c ] ; No
As regras denidas também podem ser usadas para decompor uma lista em suas componentes. Para checar isso, basta realizar a seguinte consulta:
6 ?− conc ( L1 , L2 , [ a , b , c ] ). L1 = [ ] L2 = [ a , b , c ] ; L1 = [ a ] L2 = [ b , c ] ; L1 = [ a , b ] L2 = [ c ] ; L1 = [ a , b , c ] L2 = [ ] ; No
A adição de um elemento à uma lista pode ser denida de modo simples. Para isso, basta inserir o elemento no início da lista, esta relação é denida através da seguinte regra:
i n s e r e (X, L , [X | L ] ).
com isso, pode-se realizar as seguintes inserções:
7 ?− i n s e r e ( a , [ b , c ] , L ). L = [ a , b , c ] ; No 8 ?− i n s e r e ( [ 1 , 2 ] , 3 , L ). L = [ [ 1 , 2 ] | 3 ] ; No
A exclusão de um elemento pode ser implementada através das seguintes regras:
e x c l u i (X, [X | T a i l ] , T a i l ). e x c l u i (X, [Y | T a i l ] , [Y | T a i l 1 ] ) :− e x c l u i (X, T a i l , T a i l 1 ).
a primeira regra é utilizada quando o elemento que se deseja excluir corresponde à cabeça da lista. Já a segunda regra exclui um elemento que pertence a cauda da lista. Vale
salientar que esta implementação não exclui todos os elementos existentes na lista que correspondam ao elemento passado como argumento. Denidas as regras podem ser realizadas as seguintes consultas: 9 ?− e x c l u i ( a , [ a , b , c ] , L ). L = [ b , c ] ; No 10 ?− e x c l u i ( b , [ a , b , c ] , L ). L = [ a , c ] ; No 11 ?− e x c l u i ( c , [ a , c , b , c ] , L ). L = [ a , b , c ] ; L = [ a , c , b ] ; No
Existem diversas outras operações nativas de Prolog, um exercício interessante seria realizar a implementação de algumas operações. Pode-se, por exemplo, criar uma operação para checar se uma lista está contida em outra ou uma operação para realizar permutação dos elementos.
Geralmente, quando se escreve uma expressão matemática a notação inxa é utilizada, por exemplo 2 ∗a+b∗c, onde 2 , a, b e c são argumentos e + e ∗ são operadores. Em Prolog uma expressão é representada internamente como uma árvore, assim a expressão anterior seria representada pela árvore da Figura 4. Uma maneira de representar em Prolog a expressão em questão utiliza notação prexa, a expressão seria representada como +(∗(2, a), ∗(b, c)). Porém, por ser mais usual a representação inxa também é compreendida pela linguagem.
Figura 4: Árvore para a expressão 2 ∗ a + b ∗ c
Prolog possui denidos operadores para as quatro operações: +, −, ∗, /, para realizar soma, subtração, multiplicação e divisão, respectivamente. Para se obter o resultado de uma operação é necessário utilizar o operador is, tal como ilustrado nas consultas abaixo: 3 ?− X i s 2 + 3. X = 5 ; No 4 ?− X i s 4 − 1. X = 3 ; No 5 ?− X i s 2 ∗ 5.