Eu estou apenas começando o meu 5º período na faculdade e me sinto especialmente motivado a continuar meu curso, apesar de dois de meus períodos não terem sido tão bons quanto eu gostaria. Essa motivação se deu em parte por uma matéria que fiz no penúltimo semestre: Lógica para Computação.
Nessa matéria, nós aprendemos coisas bastante familiares sobre lógica, como o que se chama de lógica proposicional - coisa com que concurseiros já estão familiarizados - e alguns assuntos mais avançados, como a lógica de predicados e o PROLOG, a "programação lógica". De início, eu pensei que seria uma matéria parecida com outra que tive logo quando entrei na universidade, porque havia uma grande interseção nas duas. Mas da metade pro fim, a lógica para computação tomou um caminho menos matemático e mais conceitual. A segunda e última prova podia ser inteiramente feita em Prolog, e deveria ser feita em casa e enviada para o professor no prazo de uma semana. Então, eu teria sete dias para aprender a linguagem (já que eu tinha o péssimo hábito de não frequentar as aulas) e resolver os problemas da prova.
Eu não estava muito otimista em ser aprovado nessa matéria porque, mesmo tendo tirado uma nota satisfatória na primeira unidade, vi que as questões dessa prova de Prolog eram bem complexas. Podiam não ser tão difíceis quanto aparentavam, mas elas certamente me assustaram de início. Depois de alguns dias estudando a linguagem por conta própria, fiquei logo de cara apaixonado e envolvido pelo paradigma de programação. Finalmente, voltei-me para a prova. Foram dois dias de intenso pensamento e até cheguei a me transmitir resolvendo as questões na Twitch (você pode me acompanhar lá clicando aqui). Resultado final: tirei 9,5 e ganhei um novo conhecimento.
No texto de hoje, você vai conhecer a linguagem Prolog, como ela é usada e porque ela me surpreendeu. Vamos nessa!
O conceito
Como já falei, Prolog é uma abreviação da expressão "programação lógica". Isso pode parecer meio vago, mas o diferencial dessa linguagem está no seu paradigma (mais sobre paradigmas em breve). O Prolog é uma linguagem de programação declarativa. Diferentemente de linguagens imperativas, onde o programador deve passar instruções para o programa executar (exemplos: escreva "Hello World!", faça a operação 2+4 * 3), no paradigma declarativo não há nenhuma instrução, nenhum comando. Explicando de forma simplificada, basta dizer (declarar) ao programa qual é o problema que queremos resolver, e ele faz o resto por nós. Parece bom? Pois é. Mas não pense que é tão fácil quanto parece. Eu ainda não disse o que significa "declarar o problema".
Entendendo a linguagem
O Prolog pode ser dividido em duas partes: o console, ou terminal, e um arquivo separado, chamado de base de conhecimento. Essa base é a parte análoga às demais linguagens - ou seja, é aí onde escrevemos todo o nosso código em Prolog. Programar nesse paradigma segue os seguintes passos: primeiro, nós programamos a nossa base de conhecimento. Depois, salvamos o arquivo e partimos para o console. A partir daí, nós dizemos ao programa para consultar a base que acabamos de criar. Com ela em mente, podemos fazer perguntas sobre coisas que não estão explícitas ao nossos olhinhos humanos, como inferências e consequências lógicas. Para entender melhor, vamos ver isso em prática num exemplo bem simples de entender.
Fatos
Imagine que você queira montar a sua árvore genealógica e esteja planejando usar o Prolog para te ajudar nessa tarefa. Um bom lugar por onde começar a montar a árvore seria com você mesmo. Já que estamos falando de genealogia e parentesco, podemos ver você como filho dos seus pais. Agora, precisamos passar isso à nossa linguagem. Em Prolog, escrevemos o seguinte:
filho(bia, pedro).
Chamamos isso de fato. Você pode entender isso como um predicado, do ponto de vista lógico, ou como uma função, do ponto de vista matemático. Cortando as partes complicadas, entenda essa estrutura filho(x, y) como a frase x é filho de y. No exemplo acima, esse fato representa que Bia é filha de Pedro. Com isso, já dá pra fazer sua árvore genealógica, escrevendo as relações de filho entre você e seus pais, eles e seus avós, e assim sucessivamente. Vamos usar a árvore abaixo para explicar o resto das coisas.
filho(bia, pedro).
filho(bia, amanda).
filho(suelen, amanda).
filho(pedro, lucio).
filho(pedro, marta).
Perceba algumas coisas dessa estrutura:
- Bia e Suelen têm a mesma mãe, mas não o mesmo pai - na verdade, o pai de Suelen nem aparece na árvore; isso vai do modo como você quer o parentesco na árvore, mas eu decidi deixar desse jeito.
- Temos sete pessoas e três gerações. Bia e Suelen são as filhas mais novas, e Lúcio e Marta são os mais velhos.
- Os pais de Amanda, assim como o pai de Suelen, não estão na árvore.
Essas informações nos serão úteis mais tarde, tenha elas em mente.
Queries
Com essas relações escritas na base de conhecimento, já podemos fazer algumas perguntas (também chamadas de queries) para o nosso programinha. Suponha que você queira saber se Pedro é filho de Lúcio. Para perguntar isso ao Prolog, escrevemos:
filho(pedro, lucio).
O terminal retornará True, pois essa relação existe na nossa base de conhecimento. Ótimo, já é um começo. Uma coisa mais interessante a se fazer seria se, em vez de perguntar se um é filho do outro, perguntar quem são os filhos de uma pessoa, ou quem são seus pais. Para essa pergunta, usamos uma query especial:
filho(X, amanda).
Esse "X" em questão é uma variável. O que acontece na prática com essa query é que o Prolog vai dar uma olhada na base de conhecimento e procurar as relações filho() em que o termo amanda esteja no segundo argumento - ou seja, aquelas em que Amanda é mãe. Na resposta que recebermos do console, veremos que o X assume todos os valores que estão nessas relações específicas. Aqui está a saída da query:
X = bia;
X = suelen.
Certo, tudo isso é muito bonito, mas se você parar pra pensar, não é lá muito útil. Tudo bem, dá pra ver pais e filhos na árvore, mas isso não é muita coisa. Seria bom mesmo ver também várias outras relações familiares, como avós, netos, irmãos, primos... Tem como fazer isso? A resposta é sim. Porém, é preciso ir além dos fatos e, parafraseando um grande personagem de um certo filme, "pensar em quatro dimensões".
Regras
As regras são um conceito parecido com as definições de funções que você encontraria em qualquer linguagem imperativa. Quando aplicado ao paradigma declarativo, torna-se uma maneira de extrair implicações lógicas dos fatos estabelecidos na base de conhecimento.
Vamos voltar para o nosso exemplo. Sabendo as relações entre pais e filhos, podemos "modelar" como seria uma relação entre avós e netos. Observe a definição abaixo:
neto(X,Z) :-
filho(X,Y),
filho(Y,Z).
Usamos a notação :- para definir uma regra. Veja também que somente variáveis foram usadas. Isso porque, numa regra, determinamos o caso geral que será aplicado a qualquer grupo de fatos. Portanto, temos a regra neto(), que tem como entrada dois parâmetros e possui em sua definição duas cláusulas filho(). A vírgula é o símbolo no Prolog correspondente ao operador lógico "e". Em resumo, podemos ler essa regra como: "X é neto de Z se X é filho de Y e Y é filho de Z". Para alcançar esse parentesco entre duas variáveis, perceba que usamos uma terceira no meio do caminho, Y, para conectar os dois. Mesmo que você não veja de primeira, estamos usando também a propriedade lógica da transitividade. Essa e outras propriedades são recursos bastante utilizados em PROLOG, e entendê-los facilita muito a vida de quem programa, tanto para ter um código mais legível quanto para o planejamento do mesmo.
Tendo essa regra estabelecida na base de conhecimento, podemos fazer queries com ela do mesmo jeito que fazemos com fatos. Cabe a você decidir quantos e quais argumentos da regra serão preenchidos, a depender das respostas que você queira obter. A seguir seguem diversas possibilidades:
neto(suelen, lucio). -> retorna false.
neto(X, lucio). -> retorna os netos de lucio, neste caso, bia.
neto(bia, X). -> retorna os avós de bia, neste caso, lucio e marta.
neto(X,Y). -> retorna todas as relações avô/neto na base.
Um detalhe relevante é que, para queries com mais de um resultado, você deve apertar a tecla ";" para iterar sobre todas as ocorrências daquela query. Para digitar um novo comando, basta apertar enter.
Em tese, você pode colocar quantas cláusulas quiser nas suas regras. Eu pessoalmente não cheguei a fazer testes mais detalhados, até porque foi um aprendizado meio às pressas (obrigado, tutoriais indianos de programação do YouTube) para uma prova relativamente curta. Também não vejo tanto conteúdo sendo produzido on-line sobre isso, mas você provavelmente não vai ter problema em encontrar conhecimento básico e intermediário por aí, só não espere os tutoriais minuciosos como os de Python e C. Antes de finalizar o texto, vou dar mais alguns detalhes desse exemplo e mostrar uma aplicação mais concreta desses conceitos.
irmao(X,Y) :-
filho(X,Z),
filho(Y,Z),
not (X=Y).
Aqui, usamos o operador not para nos certificarmos de que X e Y assumem valores distintos. Porque afinal, uma pessoa não pode ser irmã de si mesma, né? Não se preocupe se você por acaso esquecer alguns desses detalhes enquanto estiver programando. Muitos deles só nos ocorrem depois de fazermos testes, e é por isso que sempre devemos conferir nosso código e testá-lo o quanto for preciso. Aos interessados, fica como lição de casa as definições de regras para outras relações de parentesco, como primos e tios.
Tendo essa regra estabelecida na base de conhecimento, podemos fazer queries com ela do mesmo jeito que fazemos com fatos. Cabe a você decidir quantos e quais argumentos da regra serão preenchidos, a depender das respostas que você queira obter. A seguir seguem diversas possibilidades:
neto(suelen, lucio). -> retorna false.
neto(X, lucio). -> retorna os netos de lucio, neste caso, bia.
neto(bia, X). -> retorna os avós de bia, neste caso, lucio e marta.
neto(X,Y). -> retorna todas as relações avô/neto na base.
Um detalhe relevante é que, para queries com mais de um resultado, você deve apertar a tecla ";" para iterar sobre todas as ocorrências daquela query. Para digitar um novo comando, basta apertar enter.
Em tese, você pode colocar quantas cláusulas quiser nas suas regras. Eu pessoalmente não cheguei a fazer testes mais detalhados, até porque foi um aprendizado meio às pressas (obrigado, tutoriais indianos de programação do YouTube) para uma prova relativamente curta. Também não vejo tanto conteúdo sendo produzido on-line sobre isso, mas você provavelmente não vai ter problema em encontrar conhecimento básico e intermediário por aí, só não espere os tutoriais minuciosos como os de Python e C. Antes de finalizar o texto, vou dar mais alguns detalhes desse exemplo e mostrar uma aplicação mais concreta desses conceitos.
irmao(X,Y) :-
filho(X,Z),
filho(Y,Z),
not (X=Y).
Aqui, usamos o operador not para nos certificarmos de que X e Y assumem valores distintos. Porque afinal, uma pessoa não pode ser irmã de si mesma, né? Não se preocupe se você por acaso esquecer alguns desses detalhes enquanto estiver programando. Muitos deles só nos ocorrem depois de fazermos testes, e é por isso que sempre devemos conferir nosso código e testá-lo o quanto for preciso. Aos interessados, fica como lição de casa as definições de regras para outras relações de parentesco, como primos e tios.
Aplicações
Agora, talvez você não esteja vendo muita aplicabilidade para essa linguagem, apesar de ser relativamente simples de se entender e bem direta. Mas é só pensar um pouquinho mais a fundo no conceito de declarar um problema em vez de instruir o computador e você encontrará a aplicação mais proeminente: a inteligência artificial. Na verdade, a linguagem foi projetada tendo como objetivo o processamento de linguagens naturais, um ramo da linguística computacional. Tanto é que o Prolog não é uma linguagem tipada como as demais, todos os dados são do tipo Termo. O que determina o seu "tipo" é a forma como se declara o termo.
Além disso, se, ao ler este pequeno guia, você percebeu alguma similaridade com as estruturas presentes em banco de dados, então você está certo. O modo como os fatos e as relações são estabelecidos na base de conhecimento segue uma estrutura análoga à vista em databases — as queries do Prolog, a propósito, podem ser associadas a queries em SQL, por exemplo. E foi graças a essa semelhança que eu fiz um projetinho. Mas precisarei deixá-los no cliffhanger, o texto de hoje já se estendeu demais. No próximo post, eu explico o problema e mostro como fiz a minha "solução".
Além disso, se, ao ler este pequeno guia, você percebeu alguma similaridade com as estruturas presentes em banco de dados, então você está certo. O modo como os fatos e as relações são estabelecidos na base de conhecimento segue uma estrutura análoga à vista em databases — as queries do Prolog, a propósito, podem ser associadas a queries em SQL, por exemplo. E foi graças a essa semelhança que eu fiz um projetinho. Mas precisarei deixá-los no cliffhanger, o texto de hoje já se estendeu demais. No próximo post, eu explico o problema e mostro como fiz a minha "solução".
Nenhum comentário:
Postar um comentário