Adiel Mittmann Implementação do chatterbot ELIZA na linguagem multiparadigma Oz Florianópolis 2006 Adiel Mittmann Implementação do chatterbot ELIZA na linguagem multiparadigma Oz Orientador: Antônio Carlos Mariani Membro da banca: Bernd Heinrich Storb Membro da bancadois: Edla Maria Faust Ramos Universidade Federal de Santa Catarina Curso de Ciência da Computação Florianópolis 2006 Agradecimentos Meus mais sinceros agradecimentos a estas três pessoas: Mariani: por ser um verdadeiro orientador. Bernd: por apresentar-me a fascinante ELIZA. Edla: por mostrar-me qual era meu objetivo. Agradeço a todos eles por sempre me indicar onde está o chão. Sumário Lista de Figuras Resumo 1 Introdução p. 10 1.1 Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 15 1.2 Organização do texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 15 2 O modelo de programação de Oz p. 16 2.1 Memória . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 16 2.2 Declaração de variáveis . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 20 2.3 Unicação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 21 2.4 Nomes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 23 2.5 Procedimentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 24 2.6 Controle de uxo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 29 2.7 Células . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 30 2.8 Espaços computacionais p. 31 . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Os paradigmas de Oz p. 32 3.1 Programação imperativa . . . . . . . . . . . . . . . . . . . . . . . . . . p. 32 3.2 Programação funcional . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 34 3.3 Programação em lógica . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 36 3.4 Programação orientada a objetos . . . . . . . . . . . . . . . . . . . . . p. 38 3.5 Programação por restrições . . . . . . . . . . . . . . . . . . . . . . . . . p. 39 4 O chatterbot ELIZA 4.1 A conversa de ELIZA . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 43 p. 43 4.2 O funcionamento de ELIZA . . . . . . . . . . . . . . . . . . . . . . . . p. 44 4.2.1 Scripts de ELIZA . . . . . . . . . . . . . . . . . . . . . . . . . . p. 45 4.2.2 O ciclo básico de ELIZA . . . . . . . . . . . . . . . . . . . . . . p. 45 4.2.3 Substituições simples . . . . . . . . . . . . . . . . . . . . . . . . p. 46 4.2.4 Palavras-chave . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 47 4.2.5 Regras de transformação . . . . . . . . . . . . . . . . . . . . . . p. 49 4.2.6 Padrões em regras de decomposição . . . . . . . . . . . . . . . . p. 51 4.2.7 Regras de decomposição especiais . . . . . . . . . . . . . . . . . p. 53 4.2.8 Regras de remontagem especiais . . . . . . . . . . . . . . . . . . p. 53 4.2.9 Memória . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 54 4.2.10 Reação quando não há palavras-chave . . . . . . . . . . . . . . . p. 55 5 ELIZA em Oz p. 56 5.1 Divisão em módulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 56 5.2 Interface texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 57 5.3 5.2.1 Classe Screen 5.2.2 Classe TextUserInterface 5.2.3 Procedimento Módulos de script . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 59 . . . . . . . . . . . . . . . . . . . . p. 60 . . . . . . . . . . . . . . . . . . . . . . . . . p. 61 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 61 Main 5.3.1 Mensagem de boas-vindas . . . . . . . . . . . . . . . . . . . . . p. 61 5.3.2 Substituições simples . . . . . . . . . . . . . . . . . . . . . . . . p. 62 5.3.3 Grupos de palavras . . . . . . . . . . . . . . . . . . . . . . . . . p. 62 5.3.4 Palavras-chave . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 63 5.3.5 Memória . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 65 5.3.6 Comentários . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 65 5.4 Ciclo básico de ELIZA . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 66 5.5 O cérebro de ELIZA . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 67 5.5.1 Pré-processamento 5.5.2 O procedimento . . . . . . . . . . . . . . . . . . . . p. 69 5.5.3 Substituições simples . . . . . . . . . . . . . . . . . . . . . . . . p. 70 5.5.4 Busca e ordenação de palavras-chave . . . . . . . . . . . . . . . p. 71 5.5.5 Regras de decomposição . . . . . . . . . . . . . . . . . . . . . . p. 71 5.5.6 Regras de remontagem . . . . . . . . . . . . . . . . . . . . . . . p. 72 5.5.7 Regras de transformação . . . . . . . . . . . . . . . . . . . . . . p. 72 5.5.8 Atualização da memória p. 72 5.5.9 Ausência de palavras-chave MakeAnswer 5.5.10 Pós-processamento 5.6 O gerenciador de . . . . . . . . . . . . . . . . . . . . . . . . . scripts . . . . . . . . . . . . . . . . . . . . . . p. 69 . . . . . . . . . . . . . . . . . . . . p. 72 . . . . . . . . . . . . . . . . . . . . . . . . . p. 73 . . . . . . . . . . . . . . . . . . . . . . . . . . p. 73 6 Considerações nais p. 77 Referências p. 80 Apêndice A -- Doctor.oz p. 82 Apêndice B -- Eliza.oz p. 99 Apêndice C -- TextUserInterface.oz p. 116 Lista de Figuras 2 O modelo de programação de Oz 1 A memória do modelo de programação Oz . . . . . . . . . . . . . . . . p. 17 2 Exemplo de memória de restrições . . . . . . . . . . . . . . . . . . . . . p. 18 3 Compartilhamento da memória . . . . . . . . . . . . . . . . . . . . . . p. 19 4 Exemplo de registro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 20 5 Sintaxe de declaração de variáveis . . . . . . . . . . . . . . . . . . . . . p. 20 6 Exemplo de aninhamento de variáveis . . . . . . . . . . . . . . . . . . . p. 21 7 Sintaxe da unicação . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 22 8 Restrições básicas comunicadas pelo programa da Figura 7 . . . . . . . p. 23 9 Unicações que falham . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 23 10 Sintaxe para criação de nomes . . . . . . . . . . . . . . . . . . . . . . . p. 24 11 Sintaxe para a denição de procimentos . . . . . . . . . . . . . . . . . . p. 25 12 Associação entre variáveis e procedimentos . . . . . . . . . . . . . . . . p. 25 13 Sintaxa da invocação de procedimentos . . . . . . . . . . . . . . . . . . p. 26 14 Procedimento criando novos procedimentos . . . . . . . . . . . . . . . . p. 26 15 Variáveis globais com escopo léxico p. 27 16 Conteúdo da memória após execução do programa da Figura 15 17 Exemplo da construção 18 Exemplo de criação de case . . . . . . . . . . . . . . . . . . . . . . . . p. 28 . . . . . . . . . . . . . . . . . . . . . . . . p. 29 threads . . . . . . . . . . . . . . . . . . . . . . . p. 31 3 Os paradigmas de Oz 19 Atalhos para utilização de células 20 Sintaxe dos 21 Ordenação no paradigma imperativo . . . . . . . . . . . . . . . . . . . p. 34 22 Procedimentos como funções . . . . . . . . . . . . . . . . . . . . . . . . p. 35 loops em Oz . . . . . . . . . . . . . . . . . . . . . p. 33 . . . . . . . . . . . . . . . . . . . . . . . . . . p. 34 23 Declaração de funções . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 36 24 Ordenação na programação funcional . . . . . . . . . . . . . . . . . . . p. 36 25 Append em Oz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 37 26 Uma classe que implementa um contador . . . . . . . . . . . . . . . . . p. 38 27 Exemplo de programação por restrições . . . . . . . . . . . . . . . . . . p. 40 28 Problema do envio de dinheiro em Oz . . . . . . . . . . . . . . . . . . . p. 41 29 Ordenação no paradigma de programação por restrições . . . . . . . . . p. 41 4 O chatterbot ELIZA 30 Uma conversa com ELIZA . . . . . . . . . . . . . . . . . . . . . . . . . p. 44 31 Substituições simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 46 32 Exemplo de aplicação de substituições simples . . . . . . . . . . . . . . p. 47 33 Identicação de palavras-chave . . . . . . . . . . . . . . . . . . . . . . . p. 48 34 Regras associadas a palavras-chave . . . . . . . . . . . . . . . . . . . . p. 49 35 Pilha de palavras-chave . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 50 36 Transformação de frase do usuário . . . . . . . . . . . . . . . . . . . . . p. 51 37 Exemplo de padrão simples . . . . . . . . . . . . . . . . . . . . . . . . p. 52 38 Exemplo de grupos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 52 39 Exemplo de alternativas . . . . . . . . . . . . . . . . . . . . . . . . . . p. 52 40 Memória de ELIZA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 54 5 ELIZA em Oz chatterbot num sistema Unix. 41 Interação entre usuário e . . . . . . . . . p. 58 42 A classe Screen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 59 43 A classe TextUserInterface 44 Denição de mensagem de boas-vindas 45 Exemplo de substituições simples . . . . . . . . . . . . . . . . . . . . . . . p. 60 . . . . . . . . . . . . . . . . . . p. 62 . . . . . . . . . . . . . . . . . . . . . p. 62 46 Exemplo de denição de grupos . . . . . . . . . . . . . . . . . . . . . . p. 62 47 Exemplo de denição de palavras-chave . . . . . . . . . . . . . . . . . . p. 63 48 Exemplo de denição para a memória de ELIZA . . . . . . . . . . . . . p. 65 49 Exemplo de comentários . . . . . . . . . . . . . . . . . . . . . . . . . . p. 66 50 A classe Eliza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 66 51 A classe Brain . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 67 52 O método 53 Sumário do método 54 Procedimentos locais de MakeAnswerFromPhrases (I) . . . . . . . . . . p. 70 55 Procedimentos locais de MakeAnswerFromPhrases (II) . . . . . . . . . . p. 71 56 Procedimentos locais de MakeAnswerFromPhrases (III) . . . . . . . . . p. 72 57 A classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p. 74 makeAnswer Script . . . . . . . . . . . . . . . . . . . . . . . . . . . makeAnswer (Input Result) . . . . . . . . . . . . p. 68 p. 68 Resumo Linguagens multiparadigma são linguagens que disponibilizam ao programador vários paradigmas de programação. Desta forma, o programador pode, para cada problema que tem de resolver, utilizar o paradigma de programação mais apropriado, sem ter de recorrer a mais de uma linguagem de programação. Este trabalho é um estudo exploratório sobre a utilização destas linguagens multiparadigma. Para realização do estudo, o chatterbot ELIZA, um programa clássico e bastante conhecido, foi implementado na linguagem Oz, uma linguagem multiparadigma. Palavras-chave: linguagens multiparadigma, ELIZA, Oz. 10 1 Introdução As linguagens de programação existem em grande número. No entanto, as linguagens de programação não são todas radicalmente diferentes entre si. Se um programador está habituado a utilizar uma determinada linguagem de programação, ele também se sentirá confortável programando em algumas outras linguagens similares, ao passo de que sentirá diculdades ao tentar programar em linguagens que diferem muito daquela que ele conhece. As linguagens similares àquela usual do programador geralmente o são porque pertencem ao mesmo paradigma de programação. Assim, um programador que sempre utilizou a linguagem Pascal para escrever seus programas terá de enfrentar grandes obstáculos quando precisar escrever um programa em Prolog, ainda que facilmente aprenderá a escrever programas em C. Isto ocorre porque Pascal e C são representantes do mesmo paradigma, o chamado paradigma imperativo de programação, enquanto Prolog é um exemplo de uma linguagem que segue o paradigma de programação em lógica. Mas por que existem diferentes paradigmas de programação? Todos os paradigmas de programação têm o mesmo poder computacional, isto é, todo programa escrito em uma linguagem de um dado paradigma pode ser escrito em uma segunda linguagem de um outro paradigma. No entanto, as soluções para um problema especíco podem ter atributos muito diferentes quando implementadas em linguagens de paradigmas distintos. As soluções podem variar, por exemplo, no que se refere a: • Velocidade de implementação. Alguns problemas podem ser rapidamente implemen- tados em certos paradigmas, enquanto a implementação em outros pode se tornar demorada. • Velocidade de execução. O programador geralmente tem de se preocupar com o tempo que seu programa levará para resolver o problema quando o programa for executado. Mesmo que um problema possa ser facilmente resolvido utilizando-se uma linguagem que segue um determinado paradigma, o tempo que o programa 11 levará para executar pode ser simplesmente muito grande para que esta linguagem seja adotada. • Elegância. Um programa escrito em uma linguagem de um certo paradigma também pode parecer deselegante. Um programa que resolva o mesmo problema mas escrito em uma linguagem de outro paradigma pode apresentar-se com mais elegância. A elegância de um programa é uma característica que só pode ser avaliada subjetivamente. As outras duas características, apesar de serem mais objetivas, ainda assim apresentam certos empecilhos ao serem avaliadas: como provar que o programa sendo avaliado é, com relação a estas características, o melhor possível? Um programador pode escrever um programa e, em seguida, um segundo programador ou ele mesmo pode escrever, na mesma linguagem de programação, um outro programa que tome menos tempo de desenvolvimento e que execute mais velozmente. Apesar dos critérios subjetivos de avaliação dos programas escritos em linguagens de diferentes paradigmas, sempre houve interesse na busca por novos modos de expressão para as linguagens de programação. Um dos ramos da pesquisa sobre linguagens de programação lida com as chamadas linguagens multiparadigma. O objetivo das linguagens multiparadigma é permitir ao programador expressar-se da melhor maneira possível ao resolver um problema especíco. O programador pode, então, para cada problema que lhe seja apresentado, escolher o paradigma que ele crê o levará mais facilmente a atingir uma solução que possa ser escrita rapidamente, que execute velozmente e que seja elegante. E poderá fazê-lo sem ter de trocar de linguagem de programação. A linguagem Oz é uma destas linguagens multiparadigma. Em Oz o programador pode, para cada problema, optar por um paradigma para resolvê-lo ou, o que é freqüente, escrever um programa híbrido em que mais de um paradigma é empregado. Não raro um determinado programa (ou trecho dele) dicilmente pode ser identicado como pertencente a um único paradigma, sendo mais facilmente classicado como uma mescla de dois ou mais paradigmas. Os principais paradigmas presentes em Oz são: programação imperativa, programação funcional, programação em lógica, programação orientada a objetos e programação por restrições. Outros exemplos signicativos de linguagens que suportam mais de um paradigma de programação são: • Curry. Curry é uma linguagem de programação universal que tem como objetivo amalgamar os paradigmas de programação declarativos mais importantes, a saber, 12 programação funcional e programação em lógica. [Antoy e Hanus] Ainda que Curry suporte apenas dois paradigmas, estes paradigmas não são tradicionalmente reunidos em uma única linguagem. • Leda. Em Leda os principais paradigmas são: programação imperativa, programa- ção orientada a objetos, programação funcional e programação em lógica [Budd 1994]. A linguagem Oz foi escolhida para este trabalho por estas razões: • Oz provê ao programador muitos paradigmas. Poucas linguagens poderiam ser comparadas a Oz no que se refere à quantidade de paradigmas. • Oz tem várias publicações e documentação abundante, todas facilmente acessíveis através da internet. • Há um ambiente, chamado Mozart, que implementa um compilador para a linguagem Oz. Este ambiente pode ser obtido através da internet. Tanto as publicações e a documentação de Oz quanto o ambiente Mozart estão disponíveis gratuitamente no site http://www.mozart-oz.org/. Para explorar a linguagem Oz, este trabalho apresenta uma implementação de um chatterbot. substantivo bot A palavra chatter chatterbot é formada por duas palavras inglesas: signica, entre outros, é uma variante de robot (robô) palavrório e tagarelice chatter e [Houaiss 2003] bot. 1 . O Já que tem um signicado especializado: Um programa que imita o comportamento de um humano, por exemplo fazendo consultas a mecanismos de busca ou participando em uma sala de bate-papo ou em discussões em IRC. [The American Heritage Dictionary of the English Language 2000]. Um chatterbot é, portanto, um programa de computador que imita o comportamento humano através de uma conversa trivial. Com esta denição concorda [Paradiso e L'Abbate 2001]: O conceito de sacional. chatterbot é central para um sistema interativo e conver- Ele consiste de um sistema de software que tenta simular a conversação ou palavrório [chatter] de um ser humano, freqüentemente entretendo o usuário com algum tipo de conversa leve [smalltalk]. Também corrobora esta denição o que diz [Kalaiyarasi, Parthasarathi e Geetha 2003]: 1 O verbete completo é: chilro; charla, tagarelice; palavrório, palavreado oco; sons rápidos e inarticulados; trepidação, vibração; chio; ruído (esp. batida de dentes). [Houaiss 2003] 13 Um robô para conversação é chamado de chatterbot. É um programa desenvolvido com o propósito de permitir ao usuário interagir com o sistema. Um chatterbot é um software de inteligência articial que simula conversação humana e permite comunicação em linguagem natural entre homem e máquina. Uma característica importante que estas duas denições de é que os humanos, ao conversarem com chatterbots, chatterbot não mencionam sempre o fazem digitando, nunca utilizando voz. É o que arma [Laven 2005]: Um chatterbot é um programa que tenta simular uma conversa digitada, com o objetivo de, ao menos temporariamente, enganar um humano, fazendo-o pensar que estava conversando com outra pessoa. Pode-se dizer que as características que denem um chatterbot são, em linhas gerais, as seguintes: • Chatterbots são programas de computador que tentam se comunicar com seres hu- manos utilizando linguagem natural. O comunicações simplesmente convencer chatterbot às vezes tem como objetivo nestas o humano de que ele não fala com um bot, mas com outro humano. É precisamente o que acontece em testes como o utilizado no Prêmio Loebner [Loebner Prize]: os chatterbots devem convencer os juízes de que eles conversam com pessoas e não com programas de computador. • A conversa estabelecida por um dade. Ainda que chatterbot é tipicamente trivial, leve, sem complexi- chatterbots podem conversar sobre tópicos especícos e ter muitas informações acerca de um tema, a conversa em geral não tem profundidade. • A comunicação entre humanos e chatterbots é feita sempre através de digitação. Chatterbots atualmente não ouvem e nem tampouco falam2 . chatterbot surgiu na década de 60 e chamava-se ELIZA [Weizenbaum 1966]. Apesar de seu autor em nenhum momento ter utilizado a palavra chatterbot, ELIZA é o arquétipo de um chatterbot, ao ponto de [Bickmore 1999] dizer que a maior parte destes sistemas [os chatterbots] são descendentes diretos de ELIZA. De acordo com O primeiro [Vrajitoru 2004], o programa provou ser surpreendentemente eciente em manter a atenção das pessoas durante a conversa e o sucesso do programa original inuenciou o desenvolvimento de muitos outros. 2 Não ouvem e nem falam, mas por vezes digitam. Ao apresentar suas objeções ao Prêmio Loebner, [Hutchens 1996] lamenta: infelizmente tenho de simular velocidade de digitação, pausas para pensar, erros de digitação, etc. para ter uma chance de vencer. 14 Outro chatterbot clássico é PARRY [Colby 1975]. PARRY simula o comportamento de um paciente paranóico tão bem que, segundo [Mauldin 1994], Colby [o criador de PARRY] submeteu-o a testes cegos com médicos que questionaram tanto o programa quanto três pacientes humanos diagnosticados como paranóicos. Revisões das transcrições feitas tanto por psiquiatras quanto por cientistas da computação mostraram que nenhum dos dois grupos conseguiu distinguir os pacientes humanos do computador. Um chatterbot contemporâneo que goza de muita popularidade é A.L.I.C.E. [ALICE Bot], cujo comportamento pode ser programado através de uma linguagem própria. Esta lin- Articial Intelligence Markup Language guagem, batizada de AIML ( ), é baseada no padrão XML. Para aplicações desta linguagem, veja-se, por exemplo, [Neto et al. 2004] e [Leonhardt 2003]. Ainda que a trivialidade da conversa dos chatterbots esteja na própria denição do termo, eles nem por isso deixam de ter aplicações práticas. A educação é uma das áreas em que os chatterbots têm sido utilizados. Segundo [Sganderla, Ferrari e Geyer 2003], chatterbots representam um grande potencial como agentes pedagógicos, pois possuem autonomia e desenvoltura para direcionar o assunto do estudo de forma natural, sem prender-se a respostas xas e programadas para serem ativadas em determinados momentos, e talvez esta seja a característica que melhor os diferenciem dos agentes pedagógicos comuns. O • chatterbot ELIZA foi escolhido pelos seguintes motivos: Trata-se de um problema com uma complexidade adequada. O objetivo deste trabalho não é a implementação em si, mas a implementação utilizando a linguagem Oz. Por este motivo não seria interessante a implementação de um programa muito complexo, pois o foco do trabalho caria deslocado; nem a implementação de um programa muito simples, que seria incapaz de aproveitar a capacidade que Oz oferece. • ELIZA possibilita a utilização de vários paradigmas. • É um programa clássico. Desde sua concepção, em 1966 ([Weizenbaum 1966]), ELIZA tem sido freqüente alvo de comentários e várias implementações foram escritas. 15 1.1 Objetivos O objetivo geral deste trabalho é realizar um estudo exploratório sobre a utilização de linguagens multiparadigma. São objetivos especícos deste trabalho: • Descrever o modelo de programação de Oz, que foi a linguagem multiparadigma escolhida para este trabalho. • Implementar a solução de um problema que favoreça o uso de diversos paradigmas. Este problema é o chatterbot ELIZA. 1.2 Organização do texto A linguagem Oz, por não ser convencional, mereceu dois capítulos deste trabalho. O primeiro deles, o Capítulo 2, trata do modelo de programação de Oz. Este é o modelo teórico que serve de base para construções mais complexas. O Capítulo 3 descreve as construções de alto nível que dão suporte aos principais paradigmas de programação de Oz. O Capítulo 4 fala sobre o chatterbot ELIZA. Os principais conceitos de ELIZA são abordados e os requisitos para uma implementação são especicados, tomando-se como base sempre o artigo em que Weizenbaum descreveu ELIZA ([Weizenbaum 1966]). O Capítulo 5 atinge o objetivo deste trabalho, que é a implementação de ELIZA na linguagem Oz. O trabalho termina com o Capítulo 6, em que as considerações nais são apresentadas. 16 2 O modelo de programação de Oz O modelo de programação de Oz constitui o cerne da linguagem Oz. Este é o modelo teórico subjacente que fornece os meios para que possam ser expressas abstrações de mais alto nível, como aquelas do Capítulo 3. Este modelo de programação é composto por uma memória à qual estão ligados um ou mais núcleo. threads. Cada thread executa seqüencialmente sentenças da chamada linguagem A linguagem núcleo é uma linguagem simples, subconjunto da linguagem Oz inte- gral, que dene apenas operações básicas. Todos programas da linguagem Oz podem ser traduzidos para esta linguagem núcleo, isto é, a linguagem Oz completa tem exatamente o mesmo poder computacional da linguagem núcleo. Este capítulo começa por apresentar, na Seção 2.1, a memória do modelo de programação de Oz. As seções seguintes introduzem incrementalmente os elementos da linguagem núcleo. 2.1 Memória 1 A memória do modelo de programação de Oz é o local onde toda informação acessível aos programas é armazenada. Esta memória é análoga à memória RAM manipulada pelos processadores dos computadores tradicionais, porém ela é mais abstrata do que as RAMs. Enquanto a memória RAM é indexada por meio de endereços numéricos e armazena valores inteiros, a memória do modelo de Oz armazena de maneira direta, por exemplo, 2 procedimentos e associações entre variáveis e valores . E estes valores podem ser, além de números inteiros, entidades bem mais complexas, que serão vistas a seu tempo. A memória do modelo de Oz é compartimentada, isto é, possui divisões internas em 1 Toda referência à memória do modelo de Oz, nos artigos em inglês, é feita utilizando a palavra e não memory. store, 2 A memória RAM, obviamente, também é capaz de armazenar procedimentos e variáveis, mas o faz de maneira indireta, interpretando os valores inteiros que ela contém de diversas formas. Ao contrário da RAM, as entidades da memória de Oz não podem ter sua representação interna examinada. 17 que informações de diferentes categorias são armazenadas, conforme mostra a Figura 1. 3 Existem quatro divisões : • A divisão de restrições, também chamada de memória de restrições, que tem informações acerca de variáveis. • A divisão de procedimentos, também chamada de memória de procedimentos, que guarda os procedimentos denidos pelo programa. • A divisão de células, também chamada de memória de células, que guarda referências mutáveis a variáveis. • A divisão de triggers, também chamada de memória de triggers, que é utilizada para avaliação preguiçosa. A primeira divisão, de restrições, será apresentada a seguir; as demais o serão no momento 4 oportuno . Figura 1 A memória do modelo de programação Oz Memória Restrições (X = Y ) ∧ (X = 1) ∧ (Z ∈ [3, 8]) ∧ (P = ξ) ∧ (C = ρ) A Proedimentos Células Triggers ξ: ρ: Y (I, P ) (proedimento) memória de restrições armazena, sob a forma de restrições, informações sobre as variáveis do programa. Ao descrever a linguagem Basic Oz, que é sublinguagem de Oz, Henz comenta ([Henz 1997]): 3 O número de divisões e também o propósito de cada uma varia conforme a apresentação do modelo. Por exemplo: em [Roy et al. 2003], a memória é apresentada com quatro compartimentos, mas o de procedimentos está mesclado com o da memória de restrições, e há um compartimento extra para os threads; em [Smolka 1995], o compartimento de triggers é inexistente, pois estes ainda não haviam sido adicionados à linguagem Oz à época em que o artigo foi escrito. 4 Nas guras, as divisões irrelevantes serão ocultadas. 18 Em um dado momento, a memória de restrições consiste de uma restrição que é uma conjunção nita de restrições básicas. Restrições básicas têm a forma: • x = y, em que x e • x = c, em que x é uma variável e y são variáveis. c é um valor. A memória de restrições, portanto, armazena uma única restrição, que é formada 5 pela conjunção de diversas restrições básicas . Uma única restrição é suciente para que a memória guarde uma quantidade arbitrária de restrições básicas, pois o conjunto de restrições é fechado sob a conjunção ([Smolka 1995]). Quando a restrição armazenada na memória implica uma restrição básica qualquer, conrma esta restrição básica; por outro lado, quando a memória implica a negação desta restrição básica, dizemos que a memória nega esta restrição básica. dizemos que a memória Assim, a restrição da memória da Figura 2 conrma a restrição básica X=3 e também Z = 3 (pois (X = Y) ∧ (Y = Z) ∧ (X = 3) → Z = 3 ), porém ela nega a restrição básica Y = 4 (pois (X = Y) ∧ (X = 3) → Y 6= 4 básica U = 5, ). No entanto, ela nem conrma nem nega a restrição pois não tem informação suciente para decidir se esta restrição básica é ou não verdadeira. Figura 2 Exemplo de memória de restrições Memória Restrições (X = Y ) ∧ (X = 3) ∧ (Y = Z) ∧ (U = V ) Quando um programa adiciona restrições básicas à memória de restrições, dizemos que ele comunica esta restrição básica à memória de restrições. Um programa só pode comunicar restrições que sejam consistentes com a memória; ele não pode comunicar restrições básicas que tornem falsa a conjunção de restrições que a memória armazena. Por isso dizemos que a memória de restrições é quadro-negro (memória) (...) monotônica, ou seja, a restrição no torna-se monotonicamente mais forte ao longo do tempo. ([Smolka, Henz e Würtz 1995]). Assim, um programa poderia comunicar a restrição básica U = 5 à memória da Figura 2, e a restrição daquela memória se tornaria mais forte. O 5 A memória de restrições da linguagem Oz completa armazena outros tipos de restrições básicas, que estão ausentes na linguagem Basic Oz. É o caso, por exemplo, de restrições de conjuntos nitos ([Müller e Müller 1997]) e de domínios nitos ([Schulte, Smolka e Würtz 1998], [Würtz e Müller 1996]). 19 programa não poderia, contudo, comunicar uma restrição como Z = 5, pois esta restrição é inconsistente com o conteúdo da memória (já que a memória nega a restrição básica Z = 5). Todos os compartimentos da memória são compartilhados por vários acordo com a Figura 3. Os threads, threads, de além de comunicarem restrições básicas à memória de restrições, podem também sincronizar-se através dela. Várias primitivas da linguagem núcleo, como será visto adiante, aguardam que a memória de restrições conrme ou negue alguma restrição básica para poderem prosseguir. Não há necessidade de os threads utili- zarem mecanismos de acesso mutuamente exclusivo para acessar a memória; o modelo é intrinsecamente concorrente. Figura 3 Compartilhamento da memória Thread Thread Thread ... Memória Restrições Proedimentos Células Triggers Na linguagem núcleo de Oz, os nomes das variáveis sempre começam com letra maiúscula e todo identicador que começa com letra maiúscula é uma variável. Desta forma, Var e Xs são nomes válidos para variáveis, mas var e xs não são. As variáveis sobre as quais a memória de restrições tem informação podem, em um dado momento, ter um dentre vários tipos. Os tipos que nos serão mais úteis são: • Os números inteiros, representados utilizando a notação decimal tradicional (96, 1176). • Os átomos, que são constantes equivalentes aos átomos de Prolog (veja, por exemplo, [Sterling e Shapiro 1994]). Átomos sempre começam com letra minúscula (hidrogenio , oxigenio ). • registros, que contém um rótulo e uma seqüencia de pares de características campos. Átomos são geralmente usados para o rótulo; para as características, Os e números e átomos são tipicamente usados. A sintaxe e também uma representação 20 alternativa em forma de árvore para os registros podem ser vistos na Figura 4. Nesta gura, vê-se um registro cujo rótulo é e origem . origem produto , que contém as características codigo Associado à característica codigo está o inteiro 488; à característica está um valor que é um registro, tendo, por sua vez, outras características e campos. • Os nomes, que tem uma seção dedicada mais à frente (Seção 2.4). Figura 4 Exemplo de registro produto di idade p no e me o em ig go or 488 maravilha 89874000 produto (odigo :488 origem :idade (nome :maravilha ep :89874000)) 2.2 Declaração de variáveis Novas variáveis são introduzidas utilizando a construção da Figura 5. Nesta gura, as variáveis termina com o end seguinte. U e V local, conforme a sintaxe têm escopo que inicia com sua declaração e Assim, qualquer sentença dentro deste escopo pode comuni- car à memória restrições básicas acerca de U e V. Um número arbitrário de variáveis pode ser declarado; neste caso foram apenas duas. Figura 5 Sintaxe de declaração de variáveis % Declaração das variáveis U V. local U V in % % Escopo de U e de V. % end O Figura 6 mostra que as declarações de variáveis podem ser aninhadas. No caso de aninhamento, valem as regras de escopo tradicionais: neste exemplo, a variável declarada duas vezes, e a variável de nome A A é pode se referir a uma de duas variáveis, 21 dependendo do escopo. utiliza diretamente os Fica claro, por conseguinte, que a memória de restrições não nomes das variáveis em suas restrições. Figura 6 Exemplo de aninhamento de variáveis local A in local A in % Qualquer restrição comunicada utilizando o nome de A % neste ponto... end % ...não surtirá efeito na variável A deste escopo. end 2.3 Unicação De acordo com [Henz, Smolka e Würtz 1993], Oz herda da programação em lógica as variáveis lógicas. Isto quer dizer que as variáveis de Oz se comportam da mesma forma que as variáveis de Prolog. A respeito das variáveis lógicas de Prolog, [Sterling e Shapiro 1994] diz: Variáveis em programas em lógica se comportam de maneira diferente de variáveis em linguagens de programação convencionais. Elas se referem a uma entidade não especicada porém única, em vez de um local de armazenamento na memória. O fato de elas se referirem a uma entidade única decorre diretamente da monotonicidade da memória de restrições. Quando Sterling fala em uma entidade não especicada, ele salienta que, em Prolog, as variáveis inicialmente estão desamarradas. O mesmo acontece em Oz: assim que uma variável é declarada, ela não está amarrada a valor algum. As variáveis podem ser amarradas a valores através de um processo chamado de unicação, também herdado de Prolog. Em Oz, a unicação é realizada utilizando-se o operador =. Em linhas gerais, a unicação torna o que houver do lado esquerdo igual ao que houver do lado direito, e no processo restrições básicas podem ser comunicadas à memória de restrições. A sintaxe da unicação na linguagem núcleo de Oz pode ser vista na Figura 7. Nesta gura, a primeira unicação comunica a restrição básica A=B à memória. Ainda que nenhuma das variáveis tenham um valor denido, esta restrição pode ser adicionada à memória e assim que A ou B forem amarrados a um valor, ambas variáveis estarão amar- radas ao mesmo valor. É o que acontece em seguida, quando a variável o valor 3. Neste momento, a memória de restrições conrma tanto A é unicada com A=3 quanto B = 3. 22 Figura 7 Sintaxe da unicação local A in local B in local C in % Unificação de variáveis A=B % Unificação de variável a valor A=3 % Unificação de valores reg (car1 :C car2 :B)=reg (car1 :2 car2 :3) end end end A terceira e última unicação da Figura 7 mostra um caso mais complexo. Nesta unicação, dois registros são unicados. As seguintes restrições básicas são comunicadas à memória de restrições em decorrência desta unicação: • C = 2. • B = 3. Vemos que a unicação faz uma espécie casamento de padrões: poderia se dizer que ela sobrepõe os dois lados da igualdade e comunica as restrições básicas que podem ser deduzidas a partir desta sobreposição. A Figura 8 mostra o conteúdo da memória de restrições enquanto o programa da Figura 7 é executado. Ao início do programa, a memória de restrições está vazia. Em seguida, duas restrições básicas são comunicadas em duas unicações sucessivas. A terceira unicação, mesmo comunicando duas restrições básicas, como visto acima, fortalece a memória de restrições com apenas uma restrição, pois a segunda (B = 3) é redundante, isto 6 é, antes mesmo de a restrição ser comunicada à memória, ela já conrmava a restrição . Não é um erro comunicar uma restrição básica que não fortaleça a memória. Nem toda unicação é válida. Existem duas situações em que as unicações falham: • Uma restrição básica inconsistente é comunicada. Como visto há pouco, a memória de restrições não permite que restrições básicas inconsistentes sejam armazenadas. 6 É claro que, por tratar-se de uma entidade abstrata, para todos os ns podemos considerar que esta segunda restrição (B = 3) também foi adicionada. O ponto importante é que nenhuma informação foi adicionada à memória com esta restrição. nova 23 Figura 8 Restrições básicas comunicadas pelo programa da Figura 7 Restrições % Iníio do programa A=B A=B A=3 (A = B) ∧ (A = 3) reg (ar1 :C ar2 :B)=reg (ar1 :2 ar2 :3) (A = B) ∧ (A = 3) ∧ (C = 2) A primeira unicação que falha da Figura 9 o faz por que a restrição 7 inconsistente , já que neste momento a memória de restrições contém • Há uma falha no casamento entre os dois lados da igualdade. A = 4 é A = 3. É o que acontece na segunda unicação que falha da Figura 9. Lá vemos que dois registros estão sendo unicados. Ambos têm o mesmo rótulo, o que é necessário para que a unicação tenha êxito, mas as características são diferentes (o primeiro tem a característica car1 , ausente no segundo; o segundo tem car2 , ausente no primeiro). Figura 9 Unicações que falham local A in A=3 A=4 % Falha! reg (car1 :A)=reg (car2 :A) % Falha! end Na unicação, o lado direito é tão passível de ter variáveis alteradas quanto o esquerdo. A unicação A=3 e 3=A tem precisamente o mesmo signicado. Esta e também as demais características mencionadas nesta seção deixam claro que o operador de unicação de Oz ( =) difere radicalmente do operador de atribuição das linguagens imperativas (o = de C e Java, o := de Pascal). 2.4 Nomes Os nomes, em Oz, são constantes que não podem ser forjadas e que não tem uma re- presentação imprimível ([Roy et al. 2003]). Como vemos, ao contrário de inteiros, nomes 7 Aqui lembre-se a armação de Sterling (citado acima) de que as variáveis lógicas não são locais de armazenamento na memória: o valor de A não pode ser redenido. 24 não podem ser forjados. Isto signica que não há como criar um programa que itere sobre todas as possibilidades de nomes, pois os nomes são entidades primitivas que não têm estrutura ([Schulte 2002]). Os nomes serão referenciados por letras gregas minúsculas. Os nomes são utilizados em Oz para fazer a ligação entre variáveis e algumas entidades complexas, como procedimentos (Seção 2.5) e células (Seção 2.7). Quando se lida com estas entidades, os nomes são manipulados implicitamente conforme necessário o programador não lida diretamente com nomes quando precisa criar um simples procedimento. Mas os nomes também podem ser criados explicitamente. A sintaxe para criação de nomes está na Figura 10. Figura 10 Sintaxe para criação de nomes local N in {NewName N} % N é amarrado a um novo nome end A semântica de {NewName N}8 1. Um novo nome ξ é a seguinte[Roy et al. 2003]: é criado. 2. A restrição básica N=ξ é comunicada à memória de restrições. 2.5 Procedimentos Os procedimentos de Oz correspondem aos procedimentos das linguagens imperativas 9 e às funções das linguagens funcionais . A sintaxe para denição de procedimentos na linguagem núcleo de Oz é mostrada na Figura 11. Note que o número de argumentos (aqui apenas 2) é variável, e pode ser zero. A semântica do programa da Figura 11 é a seguinte ([Smolka 1995]): 1. Um novo nome ξ é criado, da mesma forma que a primitiva NewName cria um novo nome. 2. A restrição básica P=ξ é comunicada à memória de restrições. 3. A associação entre o nome ξ e o procedimento sendo denido é escrita na memória de procedimentos. 8 Esta sentença ({NewName são vistos em Seção 2.5. N}) segue a sintaxe de chamada de procedimentos de Oz. Procedimentos 9 Construções equivalentes aos predicados de Prolog são apresentados na Seção 3.3. 25 Figura 11 Sintaxe para a denição de procimentos local P in % Procedimento que recebe dois argumentos, A1 e A2 proc {P A1 A2} % % Sentenças do procedimento % end end Esta associação indireta entre variáveis e procedimentos pode ser melhor compreendida através da Figura 12, que mostra o estado da memória após a execução do programa da Figura 11. Figura 12 Associação entre variáveis e procedimentos Memória Restrições Proedimentos P =ξ ξ: Variavel Nome P (proedimento) Proedimento ξ Para invocar procedimentos, utiliza-se a sintaxe mostrada na Figura 13. O programa desta gura tem a seguinte semântica [Schulte 2002]: 1. O thread executando o programa é suspenso e aguarda que a memória conrme ou negue a restrição básica P = ξ. Ao nome ξ deve estar associado, através da memória de procedimentos, um procedimento com o mesmo número de argumentos utilizados na invocação (2 neste caso). Caso estas condições não sejam satisfeitas, o processo falha. 2. Os parâmetros formais do procedimento são substituídos pelos valores utilizados na invocação. Isto é equivalente a introduzir variáveis locais no procedimento e unicá-las aos valores passados pela invocação. 3. O procedimento é executado. 26 Figura 13 Sintaxa da invocação de procedimentos % Chama o procedimento associado à variável P, % passando dois argumentos inteiros {P 3 4} Várias características importantes dos procedimentos de Oz podem ser observadas a partir das denições acima. Uma delas é a de que os procedimentos da linguagem núcleo de Oz são de primeira classe. Segundo [Smolka 1995], uma linguagem dá suporte a procedimentos de primeira classe se: • Procedimentos podem criar novos procedimentos. • Procedimentos podem ter variáveis globais com escopo léxico. • Procedimentos são referenciados através de valores de primeira classe. Vamos examinar como Oz manifesta cada um dos pontos expostos por Smolka. A primeira condição para existência de procedimentos de primeira classe diz que a linguagem deve permitir que novos procedimentos podem sejam criados. Isto não quer dizer que qualquer linguagem que permita que o usuário crie procedimentos em seu códigofonte satisfaz esta condição. Este novos de Smolka se refere a novos procedimentos criados em tempo de execução. Desta forma, programas em linguagens como C++ e Java não criam novos procedimentos; programas em linguagens como Smalltalk e Lisp criam. Em Oz, toda denição de procedimento efetivamente cria um novo procedimento. Figura 14 Procedimento criando novos procedimentos local P A B in % Procedimento que recebe um único argumento, Q, ... proc {P Q} % Que é associado a um novo procedimento. proc {Q X} X=3 end end % Duas chamadas ao procedimento associado a P. {P A} % P associará um procedimento a A {P B} % P associará um outro procedimento a B end No programa da Figura 14, o procedimento da variável é invocado um novo procedimento ao seu parâmetro procedimento de Q. P associa a cada vez que Assim, na primeira chamada ao P, através de {P A}, a variável A será associada a um novo procedimento; 27 em seguida, quando o mesmo procedimento de B P é invocado, através de {P B}, a variável será associada a um outro procedimento, também novo, e portanto diferente do que fora associado a A. Trata-se, na verdade, de uma conseqüência natural da semântica de denição de procedimentos em Oz: uma construção proc cria um novo nome, e cada nome é único, diferente de todos os outros. A segunda condição mencionada por Smolka para que haja procedimentos de primeira classe é a possibilidade de procedimentos utilizarem variáveis globais com escopo léxico. O escopo léxico a que Smolka se refere é o escopo dentro do qual uma variável está disponível ao programa através de seu nome. E com o termo variáveis globais, Smolka faz alusão a variáveis que não são locais a um determinado procedimento, e não a variáveis acessíveis a todos os procedimentos. Figura 15 Variáveis globais com escopo léxico local P A M in proc {P Q} local L in % Início do escopo de L {NewName L} proc {Q X} L=X end % Fim do escopo de L end end {P A} % P associará a A um procedimento. {A M} % O procedimento associado a A % unifica M à variável L. end No programa da Figura 15, o corpo do procedimento associado a variável local a este procedimento, se a primitiva NewName. Q, declara uma L. A esta variável é associado um novo nome, utilizando- Em seguida, o parâmetro Este último procedimento, de P Q é associado a um novo procedimento. faz referência a uma variável que não lhe é local (uma variável global, no dizer de Smolka). O importante aqui é perceber que: • A variável global L está presa dentro do procedimento associado a procedimento está dentro do escopo de L Q, e somente o procedimento de já que este Q tem uma referência a ela. • Cada invocação do procedimento de variável L P resultará em um procedimento com uma nova que está acessível somente dentro deste procedimento. 28 A situação nal da memória, após a execução do programa da Figura 15, está na Figura 16. Figura 16 Conteúdo da memória após execução do programa da Figura 15 Memória Restrições P =ξ∧L=ρ∧A= σ∧M =L Proedimentos ξ: (proedimento); σ: (proedimento) O terceiro e último ponto de Smolka diz que procedimentos devem ser referenciados através de valores de primeira classe 10 . Conforme tudo que já foi visto, ca claro que Oz satisfaz este último ponto: em Oz os procedimentos são referenciados através de nomes, que são valores de primeira classe. Ainda que linguagens imperativas modernas como Java e C++ não suportem procedimentos de primeira classe, esta característica pode ser simulada utilizando o suporte de programação orientado a objetos destas linguagens. Com efeito, pode-se mapear os três requisitos de Smolka para estas linguagens da seguinte maneira: • A criação de novos procedimentos corresponde à criação de um novo objeto. Neste caso, a invocação do procedimento corresponde à invocação de um método qualquer do objeto. • As variáveis globais com escopo léxico correspondem aos atributos do objeto. Estes atributos só estão acessíveis aos métodos do objeto. • O uso de valores de primeira classe para referenciar procedimentos corresponde à atribuição de um objeto a uma variável qualquer. Sempre que o signicado não for prejudicado, passaremos a nos referir aos procedimentos associados a variáveis através do próprio nome das variáveis. Assim, falaremos 10 Um valor de primeira classe é um valor que tem tratamento idêntico àquele dispensado aos valores mais básicos da linguagem. No caso de Oz, isto quer dizer que um procedimento está em pé de igualdade com, por exemplo, números inteiros. 29 sobre o procedimento P quando nos referimos, na verdade, procedimento associado a P através de um nome. 2.6 Controle de uxo A linguagem núcleo de Oz provê meios para que o uxo do programa seja controlado com base no valor de alguma variável. A construção para este m é está exemplicada na Figura 17. Uma sentença case case, cuja sintaxe compara o valor de uma variável com uma série de padrões, na ordem em que estes aparecem no programa. A cada um destes padrões está associada uma seqüência de sentenças. O primeiro padrão que casar com o valor da variável terá executada a seqüência de sentenças a que está associado. Uma construção case pode, opcionalmente, especicar uma seqüência de sentenças que será executada se nenhum dos padrões casarem com o valor da variável. Figura 17 Exemplo da construção case % Fuso horário para cidades, com relação a Greenwich proc {Fuso Cidade Horas} case Cidade of saoPaulo then Horas=~3 [] paris then Horas=1 [] atenas then Horas=2 else Horas=nil end end Na Figura 17, um procedimento chamado Horas. Ele recebe dois argumentos, Cidade e valor de Cidade seja, por exemplo, o átomo saoPaulo , o argumento Horas será unicado com o número com o átomo -3. O argumento Cidade Fuso é denido. é comparado com uma série de átomos. Caso o Se nenhum casamento for bem sucedido, o argumento nil . As várias alternativas denidas em um elemento léxico léxico da linguagem. palavra-chave Horas é unicado else case são separadas por []: trata-se de um A primeira alternativa é introduzida por of. A indica que o que segue são as sentenças a serem executadas caso todos os casamentos falhem. Se todos os casamentos de uma sentença ela não especicar um else, acontecerá um erro de execução. case falharem e 30 2.7 Células As variáveis do modelo de programação de Oz, como descrito na Seção 2.1, são variáveis lógicas. Uma das conseqüências disto é que uma vez que um valor seja associado à variável, esta associação jamais poderá ser desfeita. O modelo de Oz permite, contudo, que programas armazenem informação mutável: para este m existem as células. Células nada mais são do que associações entre nomes e variáveis. A variável associada ao nome pode variar com o tempo, ainda que o nome permanece sempre o mesmo. Assim, uma célula que em um momento associe o nome associar o nome ξ à variável B, sendo que ξ à variável A pode, no momento seguinte, A 6= B . Existem duas primitivas denidas pela linguagem núcleo de Oz para a manipulação de células. A primeira delas é Celula}, {NewName Inicial que cria uma nova célula. Esta primitiva age da seguinte maneira: 1. Um novo nome ξ é criado. 2. Este nome é associado, através da memória de células, à variável Inicial, que é o valor inicial para a célula. 3. A restrição Celula = ξ é comunicada à memória de restrições. A segunda primitiva da linguagem núcleo de Oz para manipulação de células é Celula Velho Novo}, utilizada para ler e gravar em células. {Exchange Ela tem a seguinte semân- tica: 1. Aguarda que a memória de restrições conrme uma restrição básica em que ξ Celula = ξ , é um nome. Se esta restrição básica não for conrmada, uma exceção é lançada. 2. Procura-se a variável restrição básica V que está associada ao nome V elho = V ξ na memória de células. A é comunicada à memória de restrições com esta restrição o programa consegue ler o valor de uma célula. 3. A associação do nome ξ é alterada: ele agora passa a estar associado à variável O exemplo da Figura 18 ilustra a utilização de células. procede da seguinte maneira: Novo. Este trecho de programa 31 Figura 18 Exemplo de criação de threads local Contador in {NewCell 0 Contador} local X in {Exchange Contador X 1} end end 1. Ele introduz uma variável local, 2. A primitiva NewCell é invocada. conrma uma restrição básica células tem a associação de 3. Uma variável local Contador. ρ Contador = ρ, em que ρ a uma variável com valor X é introduzida. da célula é unicado com Desta maneira, a memória de restrições agora A primitiva é um nome, e a memória de 0. Exchange é invocada e o valor velho X; um novo valor, 1 é então associado ao nome ρ da célula. A ação aqui descrita corresponde, efetivamente, a armazenar o valor 1 na célula e desprezar o valor antigo lá armazenado. 2.8 Espaços computacionais O modelo de programação de Oz introduz a noção de espaço computacional agrupa memória e threads; espaços computacionais. Um isto signica que toda sentença de um programa de Oz é executada no contexto de um espaço computacional. Os espaços computacionais são úteis na realização de buscas. Neste caso, os espaços computacionais são clonados e cada clone explora uma alternativa da árvore de busca. Os espaços computacionais, nestas buscas, formam uma hierarquia. Existem várias primitivas da linguagem núcleo para manipular espaços computacionais. No entanto, estas raramente são utilizadas diretamente, pois existem construções de alto nível de abstração que são mais acessíveis ao programador veja, por exemplo, a Seção 3.3. 32 3 Os paradigmas de Oz Este capítulo mostra, supercialmente, como podem ser usados alguns dos principais paradigmas de programação suportados por Oz. Os paradigmas apresentados são: programação imperativa, programação funcional, programação em lógica, programação orientada a objetos e programação por restrições. 3.1 Programação imperativa A característica mais marcante da programação imperativa é a presença de estado. Segundo [Budd 1994], o modelo imperativo é freqüentemente descrito usando-se a frase `mudanças incrementais ao estado.' Diz-se que as variáveis das linguagens imperativas armazenam estado, pois as variáveis destas linguagens podem ter seus valores mudados ao longo do tempo. As variáveis de Oz, como visto em Seção 2.1, são variáveis lógicas e portanto incapazes de armazenar estado. Oz, porém, provê suporte a estado, especialmente através das células, vistas em Seção 2.7. Uma variável de Oz pode estar associada a uma célula, e esta célula pode ter seu valor alterado ao longo do tempo. Há uma pequena indireção quando se usa células, já que, para acessar o valor de uma célula, é preciso tipicamente passar antes por uma variável que esteja associada àquela célula. Duas primitivas foram introduzidas em Seção 2.7: nova célula, e {Exchange C A B}, {NewCell C V}, para criar uma para trocar o valor de uma célula. Apesar de teori- camente sólido, este modo de utilização de células com uma mesma chamada para ler e gravar em uma célula é pouco prático e pouco elegante. A gura Figura 19 mostra dois procedimentos, P1 e P2, que são equivalentes. O segundo, porém, usa dois atalhos denidos pela linguagem Oz, um para acessar o conteúdo de uma célula e outro para alterá-lo. O primeiro atalho utilizado pelo procedimento P2 torna mais fácil o acesso ao conteúdo 33 Figura 19 Atalhos para utilização de células proc {P1} C X in % Cria célula com valor inicial 1 {NewCell C 1} % Unifica X com o valor atual da célula local A in {Exchange C A X} A=X end % Põe um novo valor, 2, na célula {Exchange C _ 2} end proc {P2} C X in % Cria célula com valor inicial 1 {NewCell C 1} X=@C % Põe um novo valor, 2, na célula C:=2 end das células. Este atalho consiste em antepor-se o símbolo @ a uma variável. Esta expressão, então, passa a denotar o conteúdo da célula associada à variável em questão. No exemplo da Figura 19, o procedimento da célula associada à variável P2, na sentença X=@C, unica X com o conteúdo C. O segundo atalho presente no procedimento uma célula. A sentença C:=2 põe o valor 2 P2 facilita a alteração do conteúdo de na célula associada à variável C. As operações realizadas por estes dois atalhos podem ser realizadas diretamente em termos da linguagem núcleo, como mostra o procedimento mento P2, P1 da Figura 19. O procedi- no entanto, é mais legível e mais sucinto. loops de Oz As declarações que podem ser inseridas nos loops são várias. Outra facilidade de Oz para a programação imperativa são os tem a sintaxe da Figura 20. Pode-se, por exemplo, iterar os itens de uma lista (for loop do tipo while (for Condicao do). while : loops. Os Item in Lista do) ou fazer um 34 Figura 20 Sintaxe dos loops em Oz for (Declarações ) do % Sentenças end A Figura 21 é um programa adaptado de [Budd 1994]. O procedimento um argumento, AList, Sort recebe que é uma célula contendo uma lista. Esta lista é ordenada pelo procedimento. Figura 21 Ordenação no paradigma imperativo proc {Sort AList} Current={NewCell nil } P={NewCell nil } X={NewCell nil } Temp={NewCell nil } in Current:=@AList for while : @Current \= nil do X:=@(@Current.value ) P:=@AList for while : @P \= @Current do if @X<(@(@P.value )) then Temp:=@(@P.value ) (@P.value ):=@X X:=@Temp end P:=@(@P.next ) end (@Current.value ):=@X Current:=@(@Current.next ) end end Neste pequeno programa pode-se perceber duas características fortes da programação imperativa: a onipresença de estado, reetida em Oz no uso constante dos operadores e :=, utilizados para acessar células; e também as estruturas de controle while e @ if. 3.2 Programação funcional Os procedimentos apresentados na Seção 2.5 não são próprios para serem utilizados na programação funcional, já que não são funções propriamente ditas. Para que haja verdadeiras funções em Oz, algumas facilidades estão disponíveis. A primeira destas facilidades permite que qualquer procedimento com mais de um argumento seja tratado como uma função. Sempre que um destes procedimentos for invo- 35 cado, seu último argumento pode ser ocultado. Neste caso, a invocação do procedimento passa a ser uma expressão que, portanto tem um valor. O valor desta expressão será o valor unicado pelo procedimento ao seu último argumento. Na Figura 22, há um procedimento, procedimento soma os valores de Calculo A e B Soma, que recebe três argumentos: e unica o resultado com C. A, B e C. Este Já o procedimento 1 faz três chamadas a este procedimento, todas elas equivalentes . As chamadas têm o seguinte signicado: Figura 22 Procedimentos como funções proc {Soma A B C} C=A+B end proc {Calculo R} % As três linhas seguintes são equivalentes {Soma 1 2 R} R={Soma 1 2 $} R={Soma 1 2} end • A primeira delas chama o procedimento da maneira mais simples. O resultado do procedimento • Soma é unicado com a variável R. A segunda invocação usa o símbolo $ para transformar o que seria uma sentença em uma expressão. Desta forma, a invocação de Soma começa a parecer a aplicação de uma função, mas ainda há o incômodo de se utilizar o $. • A terceira chamada oculta o terceiro argumento de Soma, resultando em uma ex- pressão cujo valor é justamente aquele que é unicado pelo procedimento Soma com este último parâmetro. A linguagem Oz também dene um meio para que se possa denir as funções diretamente. Para este m, em Oz existe a palavra-chave fun. Esta palavra-chave introduz a denição de uma função, e segue a mesma sintaxe de um procedimento normal. A diferença é que toda função deve ter um valor de retorno, ou seja, toda função deve terminar com uma expressão, jamais com uma instrução. A Figura 23 mostra uma função, Figura 22. A denição de Soma Soma, equivalente ao procedimento homônimo da na Figura 23, no entanto, é efetivamente uma função, possuindo valor de retorno. 1 O programa não tem problemas de execução. 36 Figura 23 Declaração de funções fun {Soma A B} A+B end Um outro exemplo está na Figura 24, adaptado diretamente de [Budd 1994]. Nesta gura, a função Sort invoca a função padrão é realizado na função local em uma lista AList, Insertion. FoldR. O trabalho principal de ordenação Esta função recebe um elemento X a ser inserido já ordenada. Figura 24 Ordenação na programação funcional fun {Sort AList} fun {Insertion X AList} case AList of nil then X|nil [] H|T then if X<H then X|AList else H|{Insertion X T} end end end in {FoldR AList Insertion nil } end 3.3 Programação em lógica A busca, na programação em lógica, está sempre implicitamente presente. Uma das implicações disto é que o programador nunca precisa se preocupar em iniciar uma busca, pois ela está sempre acontecendo. Outra implicação, porém, é a de que a busca age sempre da mesma maneira: ela faz parte do próprio modelo de execução, e seu comportamento não pode ser alterado. Em Oz a busca pode ser controlada pelo programador. chamada busca encapsulada. Isto é realizado através da Diz-se que a busca está encapsulada porque ela é programada à parte do programa que descreve o problema em si. Assim, em um módulo o programador pode escrever, declarativamente, um programa que descreve a árvore de busca de seu problema, e separar este programa em um módulo. Em outro módulo, ele pode programa 37 a estratégia de busca (por exemplo, busca em profundidade) que será utilizada para pesquisar a árvore descrita no outro módulo. Esta estratégia de busca pode ser inclusive reutilizada mais tarde com outros módulos. Na programação em lógica a busca é sempre busca em profundidade. rias buscas estão disponíveis na biblioteca padrão. Uma delas é Search.base .one , que procura por uma solução através de uma busca em profundidade. Search.base .all , Em Oz, vá- Outra busca é que também faz uma busca em profundidade, mas desta vez com o objetivo de encontrar todas as soluções do problema. O programador muito raramente precisa programar uma estratégia de busca para seu problema. Em geral, as buscas préprogramadas do módulo Search são sucientemente ecientes para serem aplicadas à maioria dos problemas. Já que as estratégias de busca vêm pré-programadas, ao programador resta tipicamente apenas descrever seu problema. Sempre que for necessário uma ramicação da árvore de busca, o programador pode utilizar a construção choice, que cria um ponto de escolha. A estratégia de busca, então, ramicará a árvore tantas vezes quantas forem as alternativas denidas pela construção Figura 25. O procedimento terceira, Zs. Append choice. Como exemplo, veja o programa da ali denido concatena duas listas, Xs e Ys, em uma No entanto, ele realiza esta tarefa de maneira não-determinística, já que cada vez que é invocado ele cria um ponto de escolha com duas alternativas. Figura 25 Append em Oz proc {Append Xs Ys Zs} choice Xs=nil Ys=Zs [] Hx Tx Tz in Xs=Hx|Tx Zs=Hx|Tz {Append Tx Ys Tz} end end fun {TodasCombinacoes Lista} {Search.base .all proc {$ Sol} X Y in {Append X Y Lista} Sol=X#Y end} end É necessário que o procedimento Append da Figura 25 seja chamado no contexto de uma busca, pois ele apenas dene como é a árvore de busca do problema, mas não 38 especica nenhuma estratégia para a busca. O procedimento faz justamente isto: chama o procedimento Append, Busca, também da Figura 25, mas no contexto de uma busca. Este procedimento inicia uma busca para encontrar toda as combinações de listas que concatenadas resultariam na lista O procedimento Lista. TodasCombinacoes da Figura 25 invoca o procedimento Search.base .all , que inicia uma busca em profundidade. Como dito acima, os procedimentos que implementam uma estratégia de busca recebem um denido na própria chamada do procedimento script. Search.base .all script Search.base .all . descrição da árvore de busca para o procedimento O procedimento Aqui, o é um procedimento Este encontraria as seguintes soluções: [1 delega a Append. retorna uma lista com todas as soluções encontra- das após uma busca em profundidade completa. Se o procedimento fosse chamado com o parâmetro script 2], nil #[1 por exemplo, a busca de 2], [1]#[2], [1 das duas listas de cada solução resulta na lista TodasCombinacoes Search.base .all 2]#nil . A concatenação [1 2]. 3.4 Programação orientada a objetos O paradigma de programação orientada a objetos, em Oz, possui vários elementos sintáticos que facilitam sua utilização. Para apresentar estes elementos sintáticos, o código da Figura 26 será utilizado como exemplo. Figura 26 Uma classe que implementa um contador class Contador attr valor meth init () valor :=0 end meth inc () valor :=@valor +1 end meth atual ($) @valor end end O programa da Figura 26 dene uma classe chamada único atributo, valor , Contador. Esta classe tem um que guarda o valor atual do contador. Possui três métodos: 39 • init : • inc : o construtor da classe. Inicializa o atributo valor para zero. aumenta em uma unidade o valor do contador. • atual : retorna o valor atual do contador. Uma declaração de classe é introduzida pela palavra-chave class, e é seguida do nome de uma variável. Esta variável conterá a denição da classe. Note-se, portanto, que as classes em Oz são cidadãs de primeira classe. Os atributos de uma classe são denidos através da palavra-chave attr. Os métodos são denidos com a palavra-chave meth. Deve seguir esta palavra-chave o nome do método, juntamente com os possíveis argumentos do método. Um método funcional, isto é, um método que se comporta como uma função, deve ter como último argumento o símbolo $ este é o caso do método atual . 3.5 Programação por restrições Como visto na Seção 2.1, a memória de restrições de Oz é uma conjunção de restrições básicas. Estas restrições básicas, no entanto, não são sucientes para que Oz tenha um bom suporte ao paradigma de programação por restrições. De acordo com [Smolka 1995], restrições expressivas como x+y = z e x∗y = z não podem ser escritas na memória; isto acontece porque estas restrições expressivas não são restrições básicas. Para resolver este problema, Oz introduz os propagadores. restrições mais expressivas que as restrições básicas. Os propagadores impõem Eles aguardam que mais dados sobre as variáveis estejam disponíveis para levar adiante suas restrições. Um exemplo é ilustrativo neste ponto: veja-se o código da Figura 27. equação X + Y = Z, sabendo-se que X =2 e Z = 5. A função ali denida resolve a O programa segue os seguintes passos: • Como o problema trabalha com números inteiros, é imprescindível que, antes de impor restrições sobre as variáveis, o programa informe o domínio das variáveis. Por esta razão a primeira linha do corpo da função das variáveis • X, Y e Z é Calcula especica que o domínio [0, 100]. A linha seguinte cria um propagador para a restrição do problema, utilizando para isto o operador =:. Este propagador ainda não tem dados sucientes para resolver 40 Figura 27 Exemplo de programação por restrições fun {Calcula} X Y Z in [X Y Z]:::0#100 X+Y=:Z X=2 Z=5 Y end o problema ele apenas conhece o domínio das variáveis envolvidas, mas ainda existem muitas soluções possíveis para o problema. • Em seguida, o procedimento restringe o valor de duas das três variáveis envolvidas no problema: dene que X=:2 e que Z=:5. Neste momento, o propagador recém-criado volta à ação e, tendo dados sucientes, determina o valor da variável • O valor encontrado de Y, neste caso 3, Y. é retornado pela função. No exemplo anterior, não houve necessidade de busca para encontrar a solução do problema. Em muitos casos, no entanto, é necessária uma etapa nal quando se utilizam propagadores: a etapa de distribuição. Nesta etapa, inicia-se uma busca para encontrar o valor de todas as variáveis do problema. O exemplo da Figura 28 resolve um problema em que a busca é necessária. O problema envolvido é o clássico problema intitulado Send more money. Este problema consiste em encontrar-se valores distintos para cada um dos dígitos correspondentes às letras S, E, N, D, M, O, R e Y, de maneira que a equação respeitada. O procedimento • SendMoreMoney SEN D + M ORE = M ON EY seja procede da seguinte maneira: O primeiro passo dado pelo procedimento é a denição do domínio das variáveis envolvidas. Como todas as variáveis do problema são dígitos, o domínio de todas é [0, 9]. • Para que não haja mais de um dígito com o mesmo valor, é criado um propagador através da chamada ao procedimento FD.distinct . Este propagador impõe a res- trição não-básica de que todas as variáveis que lhe são passadas devem ser diferentes entre si. • Em seguida, mais duas restrições são impostas: para que zero. S e M sejam diferentes de 41 Figura 28 Problema do envio de dinheiro em Oz proc {SendMoreMoney Sol} S E N D M O R Y in [S E N D M O R Y] ::: 0#9 {FD.distinct [S E N D M O R Y]} S \=: 0 M \=: 0 S * 1000 + E * 100 + M * 1000 + O * 100 =: M * 10000 + O * 1000 + N * 100 {FD.distribute ff [S E N D M O R Sol=[S E N D M O R Y] end • + N * 10 + D + R * 10 + E + E * 10 + Y Y]} A principal restrição do problema é imposta: a equação do problema é denida através de um propagador. • Por m, uma busca é iniciada através do procedimento FD.distribute . Esta busca encontrará valores para todas as variáveis do problema que satisfaçam as restrições até aqui impostas. A Figura 29 mostra um programa para ordenação de uma lista de números utilizando-se o 2 suporte de programação por restrições de Oz . Para encontrar a solução, o procedimento Sort estabelece quais são as restrições da lista ordenada que se deseja encontrar. As duas restrições necessárias para encontrar a lista ordenada são: Figura 29 Ordenação no paradigma de programação por restrições proc {Sort AList SortedList} proc {Ordered List} case List of F|(S|R) then F=<S {Ordered (S|R)} else skip end end in {FD.list {List.length AList} 0#1000 SortedList} {Ordered SortedList} for I in AList do {FD.exactly 1 SortedList I} end {FD.distribute ff SortedList} end 2 Este programa tem duas limitações: os números da lista a ser ordenada devem estar no intervalo [0, 1000] e devem ser todos diferentes entre si. 42 • Em uma lista ordenada a1 , a2 . . . an , é sempre verdade que ai < ai+1 , para todo i < n trata-se, na verdade, de uma condição não apenas necessária mas também suciente para uma lista ordenada. Esta é a restrição imposta pelo sub-procedimento Ordered. • Os componentes nais da lista ordenada não são números quaisquer; eles devem ser elementos da lista de entrada. Por este motivo, para cada elemento da lista de entrada é criado um propagador que impõe a restrição de que apenas um e apenas um elemento da lista de saída é igual a este elemento da lista de entrada. É importante notar o alto grau de declaratividade do programa da Figura 29. Nele são denidas somente as características nais da solução desejada, sem denir-se como uma solução com estas características podem ser encontradas. 43 4 O chatterbot ELIZA Neste capítulo o clássico chatterbot ELIZA, de 1966, é descrito. A Seção 4.1 mostra como o ELIZA se sai em suas conversas. Por m, a Seção 4.2 descreve como o ELIZA funciona internamente. 4.1 A conversa de ELIZA ELIZA foi o primeiro chatterbot a ser escrito, e causou certo impacto quando da sua divulgação. Com efeito, se olharmos para a transcrição da Figura 30, ELIZA realmente parece apresentar um comportamento humano. Esta conversa é a mais famosa de ELIZA, pois foi publicada junto com o artigo de Weizenbaum ([Weizenbaum 1966]). Na conversa da Figura 30, ELIZA tenta responder a grosso modo como fariam os psicoterapeutas rogerianos [Weizenbaum 1966]. De acordo com [Weizenbaum 1966], Este modo de conversação foi escolhido porque a entrevista psiquiátrica é um dos poucos exemplos de comunicação em linguagem natural diádica categorizada em que um dos participantes está livre para assumir a posição de não conhecer quase nada acerca do mundo real. Se, por exemplo, se dissesse a um psiquiatra Eu saí para um longo passeio de barco e ele respondesse Fale-me mais sobre barcos, não se assumiria que ele não sabe nada sobre barcos, mas que ele tinha algum propósito em dirigir de tal maneira a conversa subseqüente. Esta posição assumida pelo interlocutor de ELIZA, isto é, a de que o programa entende o que ele diz, é considerada importante por [Mauldin 1994]: O maior truque de ELIZA era usar perguntas para tirar uma conversa a partir do próprio usuário, com pouca ou nenhuma contribuição real do programa. Isto funciona porque a maioria das pessoas gostam de conversar sobre si mesmas, e cam felizes em acreditar que o programa está ouvindo. O efeito causado por ELIZA em alguns de seus companheiros de conversa foi tão forte 44 Figura 30 Uma conversa com ELIZA Usuário: Men are all alike. ELIZA: In what way? Usuário: They're always bugging us about something or other. ELIZA: Can you think of a specic example? Usuário: Well, my boyfriend mademe come here. ELIZA: Your boyfriend made you come here. Usuário: He says I'm depressed much of the time. ELIZA: I am sorry to hear you are depressed. Usuário: It's true. I am unhappy. ELIZA: Do you think coming here will help you not to be unhappy? Usuário: I need some help, that much seems certain. ELIZA: What would it mean to you if you got some help? Usuário: Perhaps I could learn to get along with my mother. ELIZA: Tell me more about your family. Usuário: My mother takes care of me. ELIZA: Who else in your family takes care of you? Usuário: My father. ELIZA: Your father. Usuário: You are like my father in some ways. ELIZA: What resemblance do you see? Usuário: You are not very aggressive but I think you don't me to notice that. ELIZA: What makes you think I am not very aggressive? Usuário: You don't argue with me. ELIZA: Why do you think I don't argue with you? Usuário: You are afraid of me. ELIZA: Does it please you to believe I am afraid of you? Usuário: My father is afraid of everybody. ELIZA: What else comes to mind when you think of your father? Usuário: Bullies. ELIZA: Does that have anything to do with the fact that your boyfriend made you come here? que, de acordo com Weizenbaum, foi difícil convencer algumas pessoas de que elas não estavam efetivamente conversando com um humano [Weizenbaum 1966]. 4.2 O funcionamento de ELIZA Esta seção descreve como ELIZA funciona internamente, baseando-se no artigo original de Weizenbaum ([Weizenbaum1966ELIZA]). Aqui, no entanto, não serão mencionados detalhes relativos a uma implementação especíca, como fez Weizenbaum. Ao contrário, o objetivo da seção é apresentar um modelo abstrato para ELIZA. 45 4.2.1 Scripts de ELIZA script para ELIZA contém uma série de estruturas que denem o comportamento do chatterbot. Weizenbaum já salientava que o script utilizado por ELIZA não faz parte do programa, ou seja, é apenas um dado O chatterbot ELIZA é dirigido por um script. Um manipulado por ele. [Weizenbaum 1966]. O artigo em que Weizenbaum descreve ELIZA contém um apêndice denindo um script para o chatterbot que tenta parodiar o comportamento de um psiquiatra Rogeriano. Este script foi amplamente divulgado e, freqüentemente, é tomado como parte inseparável de ELIZA. Veja, por exemplo, o que diz [Leonhardt 2003]: Dentre os chatterbots existentes, um dos mais antigos pode ser conside- rado o Eliza. Desenvolvido em 1966 pelo professor Joseph Weizenbaum no Massachussets Institute of Technology, seu objetivo é o de simular um psicanalista em uma conversa com seu paciente. Na verdade, ELIZA não só não está restrito a apenas um comportamento, como seu script pode estar em outros idiomas, e não apenas em inglês. baum, à época em que escreveu seu artigo existiam scripts Segundo Weizen- em alemão, galês e inglês [Weizenbaum 1966]. É claro que uma língua pouco exionada como o inglês é mais propícia para ELIZA, pois vários problemas de identicação e transformação de formas verbais e nominais são evitados. Nas subseções seguintes, sempre que trechos de serão convenientemente retirados do script scripts forem exemplicados, estes anexado ao artigo de Weizenbaum, que é o mais conhecido. 4.2.2 O ciclo básico de ELIZA O ciclo de funcionamento de ELIZA consiste em ler um conjunto de frases digitadas pelo usuário e gerar uma resposta. Isto signica que ELIZA só fala em resposta às frases do usuário, ou seja, dá apenas uma resposta à entrada do usuário e não o interrompe enquanto este digita a próxima entrada. A única exceção é o início da conversa, em que uma mensagem especial, que todo A mensagem no script script de ELIZA deve denir, é comunicada ao usuário. original de Weizenbaum é How do you do. Please state your problem. 1 O texto do usuário pode conter quaisquer caracteres, inclusive pontuação normal . Na 1 Weizenbaum originalmente havia posto a restrição de que o ponto de interrogação não poderia ser 46 interface original de ELIZA, a entrada do usuário deveria ser terminada por dois retornos do carro, mas isto só é aplicável a implementações de ELIZA em que o usuário interaja com o chatterbot através de interfaces somente texto e, ainda assim, este certamente é um detalhe especíco de implementação. A resposta do chatterbot 2 é sempre em maiúsculas . Ainda que Weizenbaum prova- velmente não considerasse esta uma característica decisiva para ELIZA, ela pode tornar certos aspectos da implementação mais simples (Capítulo 5). Um dos objetivos do chatterbot de Weizenbaum era que o script pudesse ser editado. Para isto, ELIZA disponibilizava uma espécie de comando, EDIT, que invocava um editor externo, ED, para que o usuário pudesse modicar o script [Weizenbaum 1966]. Não parece plausível, entretanto, que este seja um requisito para uma implementação de ELIZA, já que qualquer editor da preferência do usuário pode ser utilizado. 4.2.3 Substituições simples O processo básico de funcionamento de ELIZA consiste em ler um conjunto de frases digitadas pelo usuário e gerar uma resposta baseando-se em um script. A unidade básica de manipulação de ELIZA é a palavra (para todos efeitos, considera-se que o apóstrofo não separa palavras; assim, I'm é uma palavra). O primeiro passo feito por ELIZA após a leitura das frases do usuário é fazer uma pequena transformação através das substituições simples. Uma substituição simples é uma regra que substitui uma palavra por outra nas frases digitadas pelo usuário, conforme mostra a Figura 31. Estas regras são parte do script sendo utilizado por ELIZA. Figura 31 Substituições simples you I dont don't I you ant an't me you wont won't Alguns objetivos podem ser identicados para as substituições simples: usado, pois era interpretado de maneira especial pelo ambiente em que ELIZA era executado. restrição não é levada em conta neste trabalho, por considerarmos-na irrelevante.. 2 As respostas serão apresentadas utilizando minúsculas para facilitar a leitura. Esta 47 • Conforme exibe o lado esquerdo da Figura 31, as substituições simples são usadas para fazer conversões gramaticais. Na gura, há conversão entre pronomes de pri- I me) e de segunda pessoa (you). meira pessoa ( , Isto signica que as frases digitadas pelo usuário são verdadeiramente mutiladas durante a aplicação das substituições simples, ou seja, elas freqüentemente perdem o sentido, servindo apenas para uso interno pelo • chatterbot. Considere o exemplo da Figura 32. No lado direito da Figura 31, vemos algumas substituições simples que nada fazem além de corrigir o texto digitado pelo usuário. Se ELIZA falasse português, este seria o tipo de substituição simples que corrigiria palavras como vc e td para você e tudo, já que estas abreviações são freqüentemente usadas em bate-papos. • Por m, algumas substituições simples podem ser vistas como um meio de normalizar o texto digitado pelo usuário. Uma substituição simples que troque computers por computer é útil porque assim somente o script só necessita tratar uma única palavra (computer) em suas regras, ao invés de duas. Figura 32 Exemplo de aplicação de substituições simples Well, I don't really like omputers. I you omputers omputer Well, you don't really like omputer. O exemplo da Figura 32 mostra uma entrada do usuário passando pelo processo de aplicação das substituições simples. Aqui, ocorre a mudança de pronome de primeira para segunda pessoa (de I para you) e também uma normalização (em que computers torna-se computer). 4.2.4 Palavras-chave Após a aplicação das substituições simples, o próximo passo consiste na identicação palavras-chave presentes nas frases do usuário. Estas nada mais são do que as palavras 3 que o script considera relevante . A cada palavra-chave está associado um número de das 3 Por exemplo, para um script que tirasse dúvidas sobre a língua portuguesa, palavras como vírgula, verbo, onomatopéia seriam importantes; já para um outro cujo objetivo fosse guiar clientes em uma loja virtual de móveis, palavras como cadeira, mesa, estante seriam importantes. 48 precedência, que indica sua prioridade. Quanto maior for o número de precedência de uma palavra-chave, maior é a sua importância. A Figura 33 mostra a frase introduzida na subseção anterior com as substituições simples já aplicadas e as palavras-chaves identicadas (estão em negrito). As palavras sob as quais há o - não são palavras-chaves; as demais contém sob si seu número de precedência. Figura 33 Identicação de palavras-chave Frase: Frase: Well - you don't really like omputer 0 10 50 Na Figura 33, vemos que a sentença original do usuário foi segmentada em duas frases. Isto se deve ao fato de que ELIZA trabalha sempre com apenas uma frase. Uma frase, 4 para ELIZA, é todo texto que não contém pontuação . Assim, a vírgula contida na frase original digitada pelo usuário deniu a segmentação observada na gura. A frase que passará para os estágios seguintes do processamento é a primeira frase, da esquerda para direita, que contenha pelo menos uma palavra-chave. No caso da Figura 33, a primeira frase será descartada, pois não contém palavras-chave. A segunda frase será utilizada nos passos seguintes, já que contém três palavras-chave. As palavras-chave da frase sobrevivente são analisadas da esquerda para a direita e ordenadas em uma pilha de acordo com o seguinte critério: quando uma palavra-chave é encontrada, ela é posta no topo da pilha se seu número de precedência for maior do que o maior número de precedência das palavras-chaves já contidas na pilha; caso contrário, ela é posta no fundo da pilha. Weizenbaum observava que a pilha resultante não estará monotonicamente ordenada, mas que, de qualquer maneira, estão numa ordem útil e interessante [Weizenbaum 1966]. A pilha de palavras-chaves para a frase sobrevivente da Figura 33 conteria, portanto, as palavras-chave nesta ordem: computer, like e you. 4 A pontuação que denia segmentos na implementação de Weizenbaum era apenas o ponto e a vírgula [Weizenbaum 1966]. É difícil imaginar por que Weizenbaum não considerou também, por exemplo, o ponto e vírgula. Neste trabalho, esta restrição é ignorada: qualquer caractere de pontuação segmenta o texto do usuário. 49 4.2.5 Regras de transformação Uma vez identicada qual é a frase com que se trabalhará e também as palavras-chave desta frase, as regras de transformação podem ser aplicadas. A cada palavra-chave o script associa uma ou mais regras de transformação. Uma regra de transformação é composta por dois elementos: uma regra de decomposição e uma ou mais regras de remontagem. Este panorama pode ser visualizado na Figura 34. Esta gura mostra parte das transformações para a palavra-chave you. Figura 34 Regras associadas a palavras-chave Palavra-have you Deomposição (1) you are (2) ··· (1) you feel (2) Remontagem How long have you been (2)? Of what does feeling (2) reminds you? Do you enjoy being (2)? Do you often feel (2)? . . . Do you believe it normal to (2)? . . . Do you enjoy feeling (2)? Uma regra de decomposição dene como a frase sobrevivente do usuário (que neste ponto já teve as substituições simples aplicadas) pode ser decomposta em fragmentos menores. Esta decomposição é feita casando-se a frase do usuário com a regra de de- composição. As regras de decomposição nada mais são do que padrões através dos quais partes da frase do usuário são identicadas. Quando a frase do usuário casa com o padrão da regra de decomposição, o resultado é uma associação entre números e trechos da frase do usuário. Estes números são utilizados 50 mais tarde pelas regras de remontagem. O mecanismo mais simples que pode ser utilizado para realizar esta associação é aquele que casa um número com uma seqüencia de qualquer tamanho de palavras (inclusive nenhuma), que pode ser observado na Figura 34. Para outros tipos de casamento, veja a Seção 4.2.6. Se a frase original do usuário fosse I am deadly ill, por exemplo, esta seria transformada para You are deadly ill através das substituições simples. Nenhuma segmentação aconteceria (não há pontuação). No script original de ELIZA, tanto you quanto are são palavras-chave com número de precedência zero; neste caso, you, que está mais à esquerda na frase do usuário, estará no topo da pilha de palavras-chave, como ilustra a Figura 35. Assim sendo, as regras de decomposição associadas a esta palavra-chave seriam tentadas uma a uma. A primeira é (1) you are (2) (veja a Figura 34), que casa com a frase do usuário, fazendo (1) igual à cadeia vazia de palavras e (2) igual a deadly ill. Figura 35 Pilha de palavras-chave You are deadly ill. Pilha Palavra-have: Peso: 0 Palavra-have: Peso: 0 you are Assim que uma das regras de decomposição da palavra-chave que está no topo da pilha casa com a frase do usuário, uma das regras de remontagem associadas à regra de decomposição é aplicada. A escolha da regra de decomposição é feita da seguinte maneira: • Na primeira vez que a regra de decomposição é utilizada, a primeira regra de remontagem associada à regra de decomposição pelo • script é escolhida. Nas vezes seguintes, as próximas regras de remontagem são escolhidas seqüencialmente. • Quando todas as regras de remontagem tiverem sido utilizadas, recomeça-se da primeira. Em nosso exemplo, a primeira regra de remontagem é How long have you been (2)? Os números nas regras de remontagem fazem referência ao texto casado com os respectivos números na regra de decomposição. Aqui, portanto, ao aplicar-se esta regra 51 de remontagem, o (2) deve ser substituído pelo texto que foi previamente casado com o (2) da regra de decomposição, a saber: deadly ill. Veja a Figura 36. Figura 36 Transformação de frase do usuário Deomposição Remontagem (nada) (1) You are deadly ill (2) How long have you been deadly ill? (2) Após a aplicação da regra de remontagem, temos a frase nal que ELIZA comunicará ao usuário. Seguindo o exemplo, a frase nal seria How long have you been deadly ill? 4.2.6 Padrões em regras de decomposição Como visto nas subseções anteriores, uma regra de decomposição é um padrão que é casado com a frase do usuário. Esta subseção mostra quais são os mecanismos que podem ser utilizados nestes padrões. O primeiro mecanismo é aquele já visto anteriormente, em que um número é associado a uma seqüência qualquer de palavras (veja a Figura 37). De acordo com o artigo de Weizenbaum, o script poderia especicar a quantidade de palavras que deveriam ser casadas, ou então optar por um número indenido de palavras (esta é a única opção que este trabalho considera). Os motivos para ignorar esta funcionalidade são os seguintes: • A utilidade deste mecanismo é limitada. Em geral, não se espera que em um determinado local do padrão haja uma única palavra, mas sim um conjunto delas. Se um substantivo é esperado, por exemplo, este sempre pode estar acompanhado de um adjetivo; um verbo, de um advérbio; etc. script. • Weizenbaum não tirou proveito do mecanismo nenhuma vez em seu • Como acontece em outros casos, talvez esta seja uma facilidade da linguagem que Weizenbaum utilizava, mas que acabou sendo subutilizada. Outro mecanismo que pode ser utilizado nos padrões das regras de decomposição é aquele que faz uso de grupos5 de palavras pré-denidos pelo script. Estes grupos permi- tem que em um dado ponto da frase uma de várias palavras seja necessária para que o 5 Weizenbaum os chama de tags. 52 Figura 37 Exemplo de padrão simples Padrão (1) Texto original I love my hildren. Frase you love your hildren Resultado (1) = you love (2) = hildren your (2) 6 casamento tenha êxito . Conforme mostra a Figura 38, o um grupo chamado de family. script de Weizenbaum dene Este grupo contém palavras como mother, father, wife, etc. O mecanismo de grupos também associa um número ao texto que foi casado com o grupo. Figura 38 Exemplo de grupos your (2) (3 [family℄) (4) Texto original I think my wife is ne, but my mother doesn't. Frase you think your wife is ne Resultado (1) = you think (2) = (nada) (3) = wife (4) = is ne Padrão (1) O terceiro e último mecanismo utilizado em padrões é idêntico em funcionalidade ao mecanismo de grupos, porém a lista de palavras possíveis em um local da frase não precisa ser pré-denida. Em vez disto esta lista de alternativas é inserida diretamente no padrão, conforme mostra a Figura 39. Figura 39 Exemplo de alternativas Padrão (1) Texto original I need help. Frase you need help Resultado (1) = (2) = need (3) = help you (2 {want,need}) (3) (nada) A diferença entre estes dois últimos mecanismos é que, utilizando-se grupos, a lista de palavras é denida apenas uma vez e pode ser referenciada em várias partes do script, ao 6 Weizenbaum arma que mais de um grupo poderia ser utilizado no mesmo local de um padrão. Provavelmente trata-se, novamente, de um recurso disponível no ambiente que Weizenbaum utilizava, mas que não teve todo seu potencial explorado o script de Weizenbaum não utiliza nenhuma vez mais de um grupo em um único padrão. 53 passo que, ao inserir-se as alternativas diretamente no padrão, a lista de palavras precisa ser denida cada vez que for utilizada. Seja quais forem os mecanismos utilizados nos padrões, as regras de remontagem se comportam da mesma maneiras, já que a elas apenas interessa que conjuntos de palavras estejam associados aos números que elas utilizam. 4.2.7 Regras de decomposição especiais Um tipo especial de regra de decomposição é aquela em que simplesmente se indica que a busca por regras de decomposição deve ser reiniciada, mas utilizando-se uma outra palavra-chave. Chamemos este tipo de regra de decomposição de redirecionadoras7 . Se- gundo [Weizenbaum 1966], estas regras de decomposição servem para manter um único conjunto de regras de transformação para mais de uma palavra-chave. No script de Wei- zenbaum, por exemplo, sempre que a busca por regras de decomposição é iniciada a partir da palavra-chave maybe, a busca é redirecionada para a palavra perhaps através de uma regra de decomposição redirecionadora. ção simples resolveria o problema: Neste caso em particular, uma substitui- bastaria substituir na entrada do usuário todas as ocorrências de uma palavra pela outra. Um caso em que não se poderia utilizar as substituições simples no lugar de uma decomposição redirecionadora é o da palavra-chave why do script de Weizenbaum. Exis- tem duas regras de decomposição tradicionais para esta palavra, e somente a terceira é redirecionadora. Portanto, a busca por decomposições só será redirecionada para outra palavra-chave caso as duas primeiras regras de decomposição não casem com a frase do usuário. 4.2.8 Regras de remontagem especiais Da mesma maneira que as regras de decomposição redirecionadora, também as regras de remontagem podem ser aparecem, nos scripts, redirecionadoras8 . As regras de remontagem redirecionadoras tipicamente por último. Neste caso elas são utilizadas somente quando todas as outras regras já foram aplicadas em outros ciclos. Um segundo tipo especial de regra de remontagem pode ser usado pelo script para indicar que a palavra-chave sendo pesquisada deve ser abandonada, e que a busca deve 7 Weizenbaum não utiliza nenhum termo especial para este tipo especial de regra de decomposição. 8 Novamente, Weizenbaum não utiliza nenhum termo especíco para este conceito. 54 ser reiniciada partindo-se da próxima palavra-chave da pilha. remontagem chamemos de abandonadora9 . A este tipo de regra de O que deve ser feito quando não há mais palavras-chave é o assunto da Seção 4.2.10. 4.2.9 Memória Uma (e só uma) palavra-chave qualquer deve ser associada pelo script a um mecanismo de memória, que, no dizer de Weizenbaum, faz com que o sistema responda mais espetacularmente. [Weizenbaum 1966] Este mecanismo de memória especica precisamente quatro regras de decomposição (que não podem ser especiais), e a cada uma delas associa apenas uma regra de remontagem (que também não pode ser especial). A palavra-chave associada ao mecanismo de memória no script de Weizenbaum é your, conforme mostra a Figura 40. Figura 40 Memória de ELIZA Palavra-have: your (1) your (2) Let's disuss further why your (1) your (2) Earlier you said your . . . (1) your (2). (2). . . . (2) But your (3). Sempre que, após uma das frases do usuário ter sido escolhida para processamento, a palavra-chave à qual está associado o mecanismo de memória estiver no topo da pilha de palavras-chave, o mecanismo de memória é ativado. Neste momento, o chatterbot procede da seguinte maneira: 1. Uma das regras de decomposição do mecanismo de memória é selecionada aleatori- 10 amente, e o padrão desta regra é casado com a frase do usuário. 2. A regra de remontagem é aplicada. 3. O texto resultante é salvo em uma lista para ser utilizado mais tarde (veja Seção 4.2.10). 9 Mais uma vez, Weizenbaum não usa nenhum termo para este tipo especial de regra de remontagem. 10 Weizenbaum não leva em consideração o caso em que este casamento falha. Em seu script, as regras de decomposição utilizadas no mecanismo de memória são feitas de tal maneira que o casamento nunca falha (veja a Figura 40). 55 Este mecanismo de memória é o único meio através do qual ELIZA pode falar ao usuário algo que não seja extraído diretamente da última entrada do usuário. 4.2.10 Reação quando não há palavras-chave ELIZA pode car sem palavras-chave por três motivos: • Não foi localizada nenhuma palavra-chave no texto digitado pelo usuário. • Uma regra de remontagem abandonadora entrou em ação, porém não havia mais palavras-chave na pilha. • Não foi possível casar nenhuma das regras de decomposição da palavra-chave do topo da pilha com o texto do usuário. 11 Seja qual for o motivo, ELIZA responde ou com algum comentário ou com algum texto armazenado previamente pelo mecanismo de memória. Weizenbaum não deixa nem um pouco claro quando o texto da memória deve ser utilizado. A este respeito, diz que quando um texto sem palavras-chave é encontrado mais tarde e um certo mecanismo de contagem está em um estado particular [sic] e a pilha em questão não está vazia, então o texto transformado é impresso como resultado. De qualquer forma, uma das saídas para ELIZA quando encontrar-se sem palavraschave é responder com uma das frases armazenadas pela mecanismo de memória. A segunda saída para ELIZA é fazer um comentário livre de conteúdo [Weizenbaum 1966]. Para que isto seja possível, todo script deve denir uma série destes comentários que serão utilizados por ELIZA no momento oportuno. 11 Não é claro se neste caso a próxima palavra-chave da pilha deveria ser candidata a ter suas regras de decomposição casadas com o texto do usuário. Parece que não é o caso. De qualquer forma, as palavras-chave sempre podem ter uma regra de decomposição cujo padrão sempre case com o texto do usuário. 56 5 ELIZA em Oz Este capítulo descreve uma implementação de ELIZA na linguagem Oz. Esta implementação segue as diretrizes estabelecidas no Capítulo 4 e procura utilizar os recursos de Oz da melhor maneira possível. A Seção 5.1 mostra a divisão da implementação em módulos e explica a função de cada um deles. A Seção 5.2 apresenta a implementação de um módulo de interface somente texto. A Seção 5.3 fala sobre os módulos de script. A Seção 5.4 mostra a classe que gerencia o ciclo básico de ELIZA. A Seção 5.5, principal seção deste capítulo, mostra como a classe Brain que gerencia um gera respostas para o usuário. Por m, a Seção 5.6 descreve a classe script. Explicar o motivo/razão/azar utiliza-se nomes em inglês. 5.1 Divisão em módulos A linguagem Oz fornece um meio de dividir um programa em módulos. Estes módulos, em Oz, recebem o nome de functors. Cada um destes módulos pode denir uma interface para que outros módulos interajam com ele. Os módulos de Oz podem ser carregados dinamicamente, comportando-se como bibliotecas de vínculo dinâmico. A implementação de ELIZA é constituída dos seguintes módulos: • Módulo de interface com usuário. mente com o usuário do Este é o módulo responsável por interagir direta- chatterbot, porém não tem a funcionalidade para responder às perguntas do usuário. • Módulo de o script . O objetivo deste módulo é fornecer o script necessário para que chatterbot possa conversar. • Módulo do cérebro. Este é o módulo capaz de gerar respostas para as frases do usuário: é o módulo mais importante desta implementação. 57 O módulo de interface com usuário tem as seguintes atribuições: • Permitir ao usuário selecionar o módulo de arma, o script script a ser usado. Como a Seção 4.2.1 é um dado do programa e mais de um script pode estar disponível ao usuário num determinado momento. Cabe ao módulo de interface com usuário possibilitar a escolha de um • script. Ler as frases do usuário e mostrar as respostas do chatterbot. Esta é a principal interação entre o usuário e a interface. • Interagir com a classe Eliza para gerar as respostas a serem entregues ao usuário. Esta interação está descrita na Seção 5.4. A Seção 5.2 descreve a implementação de uma interface com o usuário em que a interação é feita através de uma linha de comando. Esta interface, contudo, é apenas uma possibilidade: poderia ser implementada uma interface gráca, uma interface Web, etc. O módulo de Este script, script script functor de dene o apesar de ser um utilizado por ELIZA para gerar as respostas. Oz, ou seja, apesar de poder conter programas arbitrários de Oz, precisa denir apenas dados não precisa denir nenhum código. Os requisitos para um módulo de script são denidos na Seção 5.3. O módulo central para a implementação é o módulo chamado Eliza. Este módulo dene três classes: • Eliza. Esta é a única classe visível aos outros módulos. É nela que acontece o ciclo básico de Eliza. Esta classe é descrita na Seção 5.4. • Brain. Este é o núcleo de toda implementação. É nesta classe que acontece o pro- cessamento da entrada do usuário e a maior parte do processo descrito em Seção 4.2. Esta classe é descrita na Seção 5.5. • Script. pelo Esta classe nada mais é do que uma interface para o chatterbot. script sendo utilizado É descrita na Seção 5.6. 5.2 Interface texto O módulo TextUserInterface dene uma interface que utiliza somente texto para comunicação com o usuário. O código-fonte deste módulo está disponível no Apêndice C. Nesta interface, a interação acontece da seguinte maneira: 58 1. O usuário chama o programa através da linha de comando. Nesta linha de comando, o usuário pode especicar que módulo de car nenhum, o módulo de script.deve ser carregado. Se não especi- script padrão (o original de Weizenbaum) é carregado. 2. O programa imprime na tela uma mensagem de apresentação que explica ao usuário como sair do programa e também a mensagem de boas-vindas especicada pelo script. 3. Acontece, então, um ciclo em que é lida uma linha de texto digitada pelo usuário e uma resposta é impressa pelo chatterbot. O aviso para que o usuário digite sua entrada é >. O sinal # precede as respostas do chatterbot. 4. O ciclo é interrompido quando o usuário entra com o texto quit. O chatterbot, então, é nalizado. Uma possível interação entre um usuário utilizando um ambiente Unix e o chatterbot pode ser observada na Figura 41. Figura 41 Interação entre usuário e chatterbot num sistema Unix. guest@localhost:~$ eliza This is Eliza. Type "quit" when finished. # HOW DO YOU DO. PLEASE STATE YOUR PROBLEM > I am glad today. # IS IT BECAUSE YOU ARE GLAD TODAY THAT YOU CAME TO ME > Bye. # I AM NOT SURE I UNDERSTAND YOU FULLY. > quit Para executar os passos de interação enumerados acima, o módulo da interface texto dene: • A classe Screen, que centraliza as funções de entrada e saída de texto. Objetos desta classe podem ler textos digitados pelo usuário e imprimir na tela. • A classe TextUserInterface. A função desta classe é servir de ponte para a comu- nicação entre o cérebro de Eliza e a interface texto. Esta classe segue a especicação dada em Seção 5.5. • O procedimento Main. Este procedimento verica os parâmetros da linha de co- mando e inicia a interação entre o chatterbot e o usuário. O resto desta seção discorre sobre estes três pontos. 59 5.2.1 Classe Screen Figura 42 A classe Screen Sreen stdin stdout init () readLine (Line) write (String) writeLine (Line) A classe Screen, 1 entrada padrão cujo diagrama está na Figura 42, lê o texto do usuário utilizando a 2 e escreve as respostas na saída padrão . Esta classe possui os seguintes métodos: • init (): o construtor desta classe, que inicializa os dois atributos, stdin e stdout , para objetos capazes de, respectivamente, ler da entrada padrão e escrever na saída padrão. Este método dene uma classe local chamada duas outras, de stdin e Open.file stdout • readLine (Line): e Open.text , TextFile, que descende de estas duas da biblioteca de Oz. Os objetos são instâncias da classe TextFile. lê uma linha digitada pelo usuário. O argumento Line é unicado com a linha lida. Caso haja uma falha durante a leitura da linha, o valor unicado com Line, • write (String): de linha. false é informando a quem chama este método que a leitura falhou. escreve o texto String na saída padrão sem adicionar uma quebra Este método é utilizado, por exemplo, para imprimir o aviso de que o 3 usuário deve entrar com seu texto . • writeLine (Line): escreve um texto seguido de uma quebra de linha. No restante deste trabalho, sempre que se falar em ler do teclado ou escrever na 1 Entrada padrão é um termo tradicionalmente utilizado para identicar o local de onde vem o texto lido por um programa. Geralmente, trata-se do teclado do usuário, mas pode ser, por exemplo, um arquivo ou o resultado de um comando. 2 Saída padrão é o termo que designa o destino do texto impresso por um programa, que geralmente é a tela, mas pode ser também um arquivo ou pode servir de entrada para algum comando. 3 Desta maneira o usuário pode digitar seu texto à direita do aviso, por exemplo > Hello!. 60 tela, convencione-se de que está efetivamente se lendo da entrada padrão e escrevendo-se na saída padrão, que em geral são o teclado e a tela, respectivamente. 5.2.2 Classe TextUserInterface Figura 43 A classe TextUserInterface TextUserInterfae sreen init () readUserInput (Input) writeLine (Line) writeAnswer (Line) Uma instância da classe TextUserInterface, cujo diagrama é mostrado na Figura 43, é passada pelo procedimento Main para um objeto da classe se comunicar com o usuário (veja Seção 5.2.3, em seguida). Eliza, que o utiliza para Os seguintes métodos são denidos para esta classe: • init (): construtor da classe. Apenas cria uma nova instância da classe põe o resultado no atributo • readUserInput (Input): Screen e screen . lê uma linha de texto do usuário. Este método procede da seguinte maneira: imprime um aviso ao usuário ( >), lê uma linha de texto do usuário e unica esta linha lida com o argumento Input. Retorna false caso a leitura falhe. • writeLine (Line): screen . chama diretamente o método O resultado é o argumento • writeAnswer (Line): gumento Line Line do objeto do atributo ser impresso na tela. escreve a resposta do na tela. writeLine chatterbot especicada através do ar- Antes de escrevê-la, porém, imprime o símbolo # para indicar que o que segue é uma resposta de ELIZA. A impressão deste símbolo é o que diferencia este método do anterior writeLine (Line). 61 5.2.3 Procedimento O procedimento Main Main deste módulo recebe o controle do programa assim que ELIZA é chamado pela linha de comando. Ele executa as seguintes etapas: 1. Verica que módulo de script deve ser carregado. O usuário pode especicar na linha functor de Oz a ser carregado. Caso o usuário não especique nenhum módulo, o módulo Doctor, que é o script original de comando o nome de um arquivo contendo um 4 de Weizenbaum , é carregado. 2. Cria um objeto da classe init (ScriptModule), (veja Seção 5.4), passando para seu construtor, o módulo que foi carregado no passo anterior. 3. Cria um objeto da classe método Eliza TextUserInterface, denida na seção anterior, e chama o chat (UserInterface) do objeto criado no passo anterior, passando-lhe este objeto recém-criado da classe TextUserInterface. O método chat (UserInterface) utilizará este objeto para se comunicar com a interface, e só retornará quando a conversa com o usuário houver terminado. 5.3 Módulos de script Os módulos de script, nesta implementação, são functors de Oz que fornecem através de sua interface todos os dados necessários para o funcionamento de ELIZA. Este módulo não contém código algum; contém apenas estruturas de dados. As subseções seguintes descrevem uma a uma as estruturas de dados que devem ser exportadas pelo módulo para que ele seja um módulo de script válido. Para exemplo de um módulo completo de script, veja o código-fonte do Apêndice A. 5.3.1 Mensagem de boas-vindas Um módulo de script deve exportar uma vindas que será comunicada pelo chatterbot string contendo uma mensagem de boas- ao usuário antes mesmo de pedir-se-lhe que digite algum texto. O fragmento da Figura 44 dene uma variável que pode ser exportada para servir de mensagem de boas vindas. 4O script certamente foi traduzido para a sintaxe de Oz. 62 Figura 44 Denição de mensagem de boas-vindas Welcome = "HOW DO YOU DO. PLEASE STATE YOUR PROBLEM" 5.3.2 Substituições simples As substituições simples de ELIZA (veja Seção 4.2.3) devem ser exportadas por módulos de script como uma lista de registros cujos rótulos são o átomo 'from' dos registros deve conter duas características: e 'to' 5 subst . Cada um . A característica especica a palavra no texto original do usuário e a característica 'to' 'from' indica a pala- vra que deve ser posta no lugar da original. A Figura 45 mostra uma possível lista de substituições pronta para ser exportada. Figura 45 Exemplo de substituições simples Substitutions = [ subst ('from' : "DONT" to : "DON'T") subst ('from' : "I'M" to : "YOU'RE") ] 5.3.3 Grupos de palavras Os grupos de palavras (veja a Seção 4.2.6) devem ser denidos através de uma lista cujos elementos são registros. como características os átomos Estes registros devem ter como rótulo o átomo name e words group (veja a Figura 46). A característica e name 6 especica o nome do grupo , que pode ser utilizado mais tarde nas regras de decomposição. A característica words é uma lista com todas as palavras que fazem parte daquele grupo. Figura 46 Exemplo de denição de grupos Groups = [ group (name : belief words : ["FEEL" "THINK" "BELIEVE" "WISH"]) ] O trecho de programa da Figura 46 dene uma variável, Groups, exportada como o conjunto de grupos denidos por um módulo de um grupo é denido, cujo nome é belief . pronta para ser script. Aqui apenas A este grupo estão associadas as palavras FEEL, THINK, BELIEVE e WISH. 5 As aspas simples aqui se devem ao fato de que tanto from quanto to são palavras reservadas da linguagem Oz. As aspas simples permitem utilizar-se átomos com nomes quaisquer. 6 Ainda que os nomes dos grupos podem ser valores quaisquer, o uso de átomos é mais apropriado do que, por exemplo, números ou strings. 63 5.3.4 Palavras-chave As palavras-chave (veja a Seção 4.2.4) são a principal informação exportada por um módulo de script, e também as estruturas mais complexas. ser exportadas por um módulo de registros com o rótulo keyword script As palavras-chave devem sob a forma de uma lista cujos elementos são (veja a Figura 47). Figura 47 Exemplo de denição de palavras-chave KEYWORDS= [ keyword (text : "YOU" rank : 0 rules : [rule (decomp : [0 "YOU" oneof (num : 0 group : belief ) "YOU" 1] reasm : [["DO YOU REALLY THINK SO"] ["BUT YOU ARE NOT SURE YOU" 1]]) rule (decomp : [0 "YOU" "WAS" 0] reasm : [equals (keyword : "WAS")])]) keyword (text : "LIKE" rank : 10 rules : [rule (decomp : [0 oneof (num : 0 alt : ["AM" "IS" "ARE"]) 0 "LIKE" 0] reasm : [equals (keyword : "DIT")]) rule (decomp : [0] reasm : [newkey ])]) keyword (text : "HOW" rank : 0 rules : [rule (equals : "WHAT")]) ] Cada um dos registros de rótulo • text : keyword deve ter três características: especíca o texto da palavra-chave, isto é, a representação textual da palavra-chave. Este texto é o que deve ser casado com a entrada do usuário. • rank : especica o número de precedência da palavra-chave. Este número pode ser 7 zero, mas precisa estar sempre presente . • rules : contém uma lista de regras de transformação, cada uma delas sendo um registro com rótulo decomp , 7 Nos scripts rule . Cada registro rule para a regra de decomposição, e por sua vez tem duas características: reasm para as regras de remontagem. de Weizenbaum, este número podia estar oculto, subentendendo-se zero. 64 A regra de decomposição pode ser uma lista, especicando neste caso um padrão. Os elementos desta lista devem ser um dos seguintes (veja Seção 4.2.6): string. string deverá casar exatamente com a entrada do usuário. • Uma • Um número. Este número corresponde àqueles padrões simples em que um número Esta é associado a uma seqüência de palavras. signicado especial: Aqui, no entanto, o número zero tem corresponde, como os demais, a uma seqüência de tamanho qualquer de palavras, porém pode aparecer várias vezes na regra de decomposição e não pode ser utilizado nas regras de remontagem. • group , utilizado para casamento com grupos. Um registro com rótulo deve ter duas características. Uma delas é num , Este registro que especica o número ao qual ao texto casado será associado. A segunda característica que deve estar presente é name , • que especica o nome do grupo. alt , Um registro com rótulo rística num utilizado para alternativas. Aqui também a caracte- é usada para indicar a que número deve ser associado o texto casado. A característica words deve ser uma lista das possíveis palavras que podem gurar naquela posição da frase. As regras de decomposição redirecionadoras (veja Seção 4.2.7) podem ser especicadas denindo apenas a característica equals equals deve estar associada a uma para o registro de rótulo rule . Neste caso, string especicando a palavra-chave a partir de onde a busca deve ser reiniciada (veja Figura 47). Já as regras de remontagem deve estar associadas à característica de rótulo rule . A característica reasm reasm do registro especica uma lista cujos elementos são as regras de remontagem. Cada regra de remontagem deve ser de um dos seguintes tipos (veja a Seção 4.2.5 e a Seção 4.2.8): • strings) Uma lista de palavras ( e números. Os números referenciam aqueles das regras de decomposição. A concatenação destas strings com o texto associado aos números resulta numa resposta apresentável ao usuário. • Um registro com rótulo equals . Este indica que a busca por respostas deve reco- meçar na palavra chave indicada pela característica • O átomo keyword deste registro. newkey , indicando que a busca deve ser reiniciada com a palavra-chave do topo da pilha. 65 • Um registro com rótulo pre . Este registro indica que um pré-processamento deve ser feito utilizando-se a lista associada à característica reasm deste registro. Esta lista pode conter palavras e números (os números aqui também fazem referências àqueles das regras de decomposição). Após este pré-processamento, a busca deve recomeçar a partir da palavra-chave indicada pela característica 5.3.5 keyword deste mesmo registro. Memória Os módulos de script também devem exportar uma outra estrutura para o correto funcionamento do mecanismo de memória de ELIZA (veja Seção 2.1). Esta estrutura, exemplicada na Figura 48, deve ser um registro cujo rótulo é o átomo registro deve ter duas características: keyword , canismo de memória deve associar-se, e memory . Este especicando a que palavra-chave o me- transfs , especicando a lista de transformações possíveis. Figura 48 Exemplo de denição para a memória de ELIZA Memory = memory (keyword : "YOUR" transfs : [ transf (decomp : [0 "YOUR" 1] reasm : ["LETS DISCUSS FURTHER WHY YOUR" 1]) transf (decomp : [0 "YOUR" 1] reasm : ["EARLIER YOU SAID YOUR" 1]) transf (decomp : [0 "YOUR" 1] reasm : ["BUT YOUR" 1]) transf (decomp : [0 "YOUR" 1] reasm : ["DOES THAT HAVE ANYTHING TO DO WITH THE FACT THAT YOUR" 1]) ]) Os elementos da lista da característica transf . A característica enquanto a característica 5.3.6 decomp reasm transfs devem ser registros com rótulo destes rótulos descreve uma regra de decomposição, descreve uma regra de remontagem. Comentários script é ser strings O último item necessário aos módulos de contexto. Os elementos desta lista devem uma lista de comentários livres de que indicam diretamente as frases 66 que servem de comentários nenhuma decomposição ou transformação acontece com estas strings. Figura 49 Exemplo de comentários Comments = [ "I AM NOT SURE I UNDERSTAND YOU FULLY." "PLEASE GO ON." "WHAT DOES THAT SUGGEST TO YOU" "DO YOU FEEL STRONGLY ABOUT DISCUSSING SUCH THINGS" ] 5.4 Ciclo básico de ELIZA O ciclo básico de ELIZA, constituindo da leitura de frases do usuário e da geração de respostas, é gerenciado pela classe Eliza, pertencente a módulo homônimo, cujo diagrama pode ser visto na Figura 50. Figura 50 A classe Eliza Eliza brain init (Module) hat (UserInterfae) A classe Eliza tem apenas dois métodos. O primeiro método é construtor da classe. Este construtor recebe como único argumento ao objeto sendo construído qual dever ser o módulo de tomada por este método é criar um objeto da classe script Brain init (Module), Module, utilizado. o que indica A única ação (veja Seção 5.5), passando script que foi recebido já que objetos da classe Eliza não interagem diretamente com scripts. adiante o módulo de O segundo método da classe implementa o ciclo básico. Eliza é chat (UserInterface). A primeira ação deste método é comunicar ao usuário a mensagem de boas-vindas, com auxílio do objeto no atributo UserInterface e do objeto armazenado brain . Após saudar o usuário, o método cal Este é o método que ChatLoop. chat (UserInterface) chama o procedimento lo- Este procedimento solicita à interface com usuário (UserInterface) que 67 uma linha do usuário seja lida, chamando o método UserInterface. readUserInput (Input) Caso a leitura falhe, o ciclo é encerrado imediatamente. Completando- se com êxito a leitura, o procedimento solicita ao objeto da classe atributo brain do objeto Brain armazenado no que gere uma resposta à entrada do usuário recém-lida. A resposta é co- municada ao usuário através do método writeAnswer (Line) do objeto UserInterface. Por m, o ciclo é reiniciado chamando-se recursivamente o procedimento ChatLoop. 5.5 O cérebro de ELIZA O cérebro de ELIZA é a classe Brain, cujo diagrama pode ser observado na Figura 51. Esta classe dene três métodos: • init (Module), um construtor. O argumento Module é o módulo de script que deve ser utilizado pelo objeto sendo criado. Este módulo é usado pelo construtor para criar um objeto da classe Script, que é então armazenado no atributo script . construtor também inicializa o atributo memory Este com a lista vazia este atributo contém a lista de respostas do mecanismo de memória, inicialmente sem elementos. • getWelcome (Answer): do atributo este método simplesmente repassa a requisição para o objeto script . • makeAnswer (Input Answer): este método é o coração do recebe uma entrada do usuário em Answer. chatterbot ELIZA. Ele Input e, após gerar uma resposta, unica-a com Todo o restante desta seção tratará deste método. Figura 51 A classe Brain Brain sript memory init (Module) getWelome (Answer) makeAnswer (Input Answer) Vários métodos das classes desta implementação de ELIZA têm procedimentos locais. No entanto, nenhum deles tem procedimentos locais na quantidade e no nível de aninhamento que o método makeAnswer (Input Answer) tem. A Figura 52 mostra uma árvore 68 com os procedimentos locais deste método, ocultando a hierarquia sob o procedimento local MakeAnswerFromPhrases. Esta hierarquia é apresentada mais tarde. Figura 52 O método makeAnswer Preproess MakeAnswerFromPhrases . . . . . . O método makeAnswer makeAnswer (Input Answer) Postproess tem três etapas, que coincidem com os três procedimentos locais. Estas etapas são as seguintes: • Pré-processamento. Esta etapa prepara o texto digitado pelo usuário para ser utili- zado pela etapa seguinte. É vista na Seção 5.5.1. • Geração de respostas. Aqui a resposta para o usuário é gerada a partir da entrada fornecida pela etapa anterior. Esta etapa é a mais complexa e está descrita ao longo de várias seções. • Pós-processamento. A resposta gerada pela etapa anterior é transformada para que possa ser diretamente apresentada ao usuário. Esta etapa é discutida na Seção 5.5.10. Estas três etapas podem ser visualizadas na Figura 53. Aqui vê-se que o corpo propriamente dito do método Result) makeAnswer (Input é composto de apenas uma linha, que chama os três procedimentos corres- pondentes às etapas acima. Figura 53 Sumário do método makeAnswer (Input Result) meth makeAnswer (Input Answer) % % Definições dos procedimentos locais... % in Answer={Postprocess {MakeAnswerFromPhrases {Preprocess Input}}} end 69 5.5.1 Pré-processamento A etapa de pré-processamento do método Eliza é realizada pelo procedimento local procedimentos locais. O argumento String makeAnswer (Input Answer) {Preprocess String}, que contém outros que este procedimento recebe é a o usuário digitou e que será pré-processada. da classe string que O procedimento age aplicando uma série de funções em seqüência, sendo que o valor de uma função é fornecido como entrada à seguinte. As funções aplicadas pelo procedimento são as seguintes: • {Uppercase String}. Retorna o parâmetro String com todas suas letras con- vertidas para maiúsculas. Esta conversão é necessário pois ELIZA trabalha apenas com letras maiúsculas. Exemplo: "Hello,ttaretyoutthere?" → "HELLO,ttAREtYOUtTHERE?" (o es- paço está representado pelo t). • {NormalizeSpaces String}. Troca todas seqüências de um ou mais espaços por apenas um espaço. Exemplo: "HELLO,ttAREtYOUtTHERE?" → "HELLO,tAREtYOUtTHERE?". • {SplitIntoPhrases String}. de acordo com a pontuação. Quebra a string do argumento String em frases, Os espaços são eliminados conforme necessário. A pontuação não faz parte das frases nais. Exemplo: "HELLO,tAREtYOUtTHERE?" → ["HELLO" "AREtYOUtTHERE"]. • {SplitIntoWords Phrases}. Quebra as frases da lista Phrases em palavras. Os espaços desaparecem durante a conversão. Exemplo: ["HELLO" "AREtYOUtTHERE?"] → [["HELLO"] ["ARE" "YOU" "THERE"]]. Os procedimentos locais acima enumerados fazem uso do suporte à programação funcional de Oz. Funções de ordem superior são utilizadas com freqüências; as células de Oz não são utilizadas; a própria solução do problema é expressa como uma composição de funções. 5.5.2 O procedimento O procedimento MakeAnswer MakeAnswer(Phrases Answer) recebe uma entrada, Phrases, que é o resultado do pré-processamento do texto digitado pelo usuário e gera uma resposta, unicando-a com Answer. Este procedimento comporta-se do seguinte modo: 70 • Como ELIZA trabalha com uma frase somente, tenta-se processar a primeira frase contida na lista é acionado. é gerado. Phrases. Caso esta lista esteja vazia, o mecanismo de memória Se este não devolver uma resposta, um comentário livre de contexto A lista Phrases pode estar vazia por dois motivos: ou o usuário não digitou nada ou todas as frases já foram processadas por chamadas anteriores a este procedimento. Phrases • A primeira frase de • Procura-se, então, por palavras-chave na frase. sofre o processo das substituições simples. Em seguida, estas palavras-chave são ordenadas seguindo o critério estabelecido por Weizenbaum. • Caso nenhuma palavra-chave tenha sido encontrada na primeira frase de Phrases, o procedimento é invocado recursivamente, mas como parâmetro recebe apenas as frases restantes de • Phrases. Utilizando-se o paradigma de programação em lógica, é iniciada uma busca por uma resposta, baseando-se nas palavras-chave encontradas na primeira frase de Phrases. Se a busca obtiver sucesso, o precidemento unica a resposta encontrada com o parâmetro Answer. Se a busca falhar, o procedimento é invocado recursivamente, mas desta vez com o parâmetro 5.5.3 Phrases igual à lista vazia. Substituições simples Figura 54 Procedimentos locais de MakeAnswerFromPhrases (I) MakeAnswerFromPhrases ApplySimpleSubstitutins GetKeywords SortKeywords Compare SubSimplify As aplicação das substituições são aplicadas a cada frase antes de esta ser vasculhada em busca de palavras-chave. A função utilizado para aplicar as substituições simples é {ApplySimpleSubstitutions(Phrase)}. O parâmetro Phrase que ela recebe é uma lista 71 de palavras que compõem a frase. Cada uma destas palavras será substituída por outra ou não de acordo com o que o Se a função "YOU" script especica. {ApplySimpleSubstitutions(Phrase)} "THERE"] e se o fosse aplicada à frase ["ARE" script sendo utilizado fosse o padrão, a resposta da função seria ["ARE" "I" "THERE"]. A função função {ApplySimpleSubstitutions(Phrase)} {MakeAnswerFromPhrases(Phrases)} dimento local do método 5.5.4 é um dos procedimentos locais do (veja Figura 54), que por sua vez é proce- makeAnswer (Input Answer) da classe Eliza. Busca e ordenação de palavras-chave A busca de palavras-chave é feita pela função Phrase}. {GetKeywords A função retorna uma lista das palavras-chave que estão presentes na frase, na ordem em que aparecem na frase. Já a função {SortKeywords Keywords} recebe uma lista de palavras-chave e as retorna ordenada de acordo com a posição na frase e o número de precedência das palavraschave. 5.5.5 Regras de decomposição Figura 55 Procedimentos locais de MakeAnswerFromPhrases (II) MakeAnswerFromPhrases Deompose Reassemble SubMath O procedimento ApplyRules UpdateMemory MakeFromRules AddToMemory {Decompose Input Pattern Map frase vinda do usuário (Input) com um padrão do tem a tarefa de confrontar uma script (Pattern) e, caso o casamento seja feito com sucesso, adicionar associações de números a conjuntos de palavras ao dicionário indicado por Map. 72 O casamento do padrão com a entrada foi implementado fazendo uso do paradigma de programação em lógica. Sempre que existe mais de uma possibilidade para o casamento de padrões, um ponto de ramicação da árvore de busca é criado utilizando-se a primitiva choice 5.5.6 de Oz. Regras de remontagem O procedimento {Reassemble Reasm Map} recebe uma regra de remontagem, Reasm, que indica como a remontagem deve ser feita, e também um dicionário, Map, que contém as associações entre números e seqüências de palavras que podem ser utilizadas pela regra de remontagem. 5.5.7 Regras de transformação O procedimento {ApplyRules Input Keywords Answer} recebe uma frase do usuá- rio, já pré-processada, e também uma lista das palavras-chave daquela frase, em Utilizando seu procedimento local MakeFromPatterns, usuário e, encontrando-a, unica-a com 5.5.8 Keywords. procura por uma resposta para o Answer. Atualização da memória Sempre que uma frase possui palavras-chave, a palavra-chave que está no topo da lista após a ordenação, juntamente com a entrada do usuário, são passados para o procedimento {UpdateMemory Input Keyword}, 5.5.9 que tenta adicionar uma nova frase à memória. Ausência de palavras-chave Figura 56 Procedimentos locais de MakeAnswerFromPhrases (III) MakeAnswerFromPhrases MakeComment RetrieveFromMemory Quando faltarem palavras-chave, o mecanismo de memória, através da função é chamado. Caso este falhe, a função {MakeContextFreeComment} ção nunca deve falhar e retornará uma resposta ao usuário. {RetrieveFromMemo é invocada. Esta fun- 73 5.5.10 Pós-processamento O pós-processamento é feito para que a resposta gerada esteja sob a forma de string, que pode então ser passada à classe de interface com usuário. 5.6 O gerenciador de scripts A classe Script do módulo Eliza, prover uma interface amigável aos cujo diagrama está na Figura 57, existe para scripts de ELIZA. Os módulos de script exportam as informações necessárias para sua utilização, mas estas informações não estão nem prontas para serem usadas e tampouco podem ser acessadas facilmente. Algumas estruturas do script também precisam ser atualizadas ao longo do tempo. Os três objetivos principais desta classe são, pois: • Deixar a informação do script pronta para ser usada. Em alguns casos faz-se neces- sário o uso de células para armazenar informação que será atualizada. É o caso das regras de remontagem, que só devem ser utilizadas uma segunda vez quando todas as outras regras disponíveis já tiverem sido usadas. Por isso células são inseridas nas regras de transformação, para que se saiba qual foi a última regra de remontagem aplicada. • Tornar mais fácil o acesso a certas informações. Por exemplo: a classe fornece um método para convenientemente se obter a estrutura de uma palavra-chave baseandose em seu texto. Assim, o cérebro de Eliza não precisa preocupar-se em fazer pesquisas nas estruturas do módulo de • script; esta busca é feita pela classe Script. Fornecer um meio transparente para a atualização das estruturas do script que necessitem ser modicadas. Estas estruturas são as regras de transformação e a lista de comentários. O construtor da classe dulo de script Script, init (Module), recebe como único parâmetro o mó- ao qual o objeto proverá acesso. É neste construtor que as modicações de algumas estruturas são feitas. As modicações feitas pelo construtor atingem apenas duas estruturas exportadas pelo módulo de script: as palavras-chave e os comentários livres de contexto. As modicações feitas nestas duas estruturas visam a adicionar células para que se saiba qual foi a última regra de remontagem aplicada. Para as palavras-chave utiliza-se 74 Figura 57 A classe Script Sript welome groups substs memory omments keywords updateRulePro init (Module) getWelome (Answer) getComments (List) getKeyword (Word Keyword) getGroup (Name Group) getSubstitutions (Substs) getMemory (Memory) updateRule (Rule) a função {ProcessKeywords Keywords} que recebe a estrutura de palavras-chave expor- tada pelo módulo de script e retorna uma nova estrutura com as células devidamente adicionadas. Estas células são adicionadas a todo registro tes são os registros que contém, sob a característica rule das palavras-chave. Es- reasm , as regras de remontagem. São o local mais adequado, portanto, para armazenar a informação sobre qual foi a última regra de remontagem aplicada. O caso da estrutura de comentários é mais simples, pois trata-se de apenas um conjunto de regras de remontagem. A adição da célula é feita diretamente no método init (Module). Algumas estruturas do módulo de script não sofrem quaisquer alterações e são retor- nadas intocadas pelos métodos de acesso da classe Script. Os métodos que agem desta maneira são os seguintes: • getWelcome ($), • getMemory ($), chatterbot. que retorna a mensagem de boas-vindas do script. que retorna a estrutura utilizada no mecanismo de memória do 75 A classe Script possui alguns métodos que retornam parte das informações de um determinado item exportado pelo módulo de script. O objetivo destes métodos é facilitar o acesso à informação. Os seguintes métodos têm este objetivo: • getKeyword (Word $): faz uma busca nas estruturas de palavra-chave para en- contrar aquela que corresponde a palavra Retorna false caso Word • getGroup (GroupName É um erro de script Word. Esta estrutura é então retornada. não seja uma palavra-chave. $): procura pelo grupo de nome GroupName, e o retorna. não existir um grupo a que se faz referência em uma regra de decomposição. • getSubstitution (Word $): ou nil caso a palavra Word retorna a palavra que deve ser posta no lugar de Word, não deva ser substituída por nenhuma outra. Os comentários, apesar de serem modicados pelo construtor da classe Script, não precisam de nenhum meio de acesso especial: são retornados diretamente pelo método getComments ($). Como mencionado acima, as regras de transformação são atualizadas para que armazene a informação de qual regra de remontagem foi utilizada por último. Esta informação nada mais é do que um contador, que precisa ser incrementado toda vez que a regra de transformação tiver uma de suas regras de remontagem aplicadas com êxito. Incrementar este contador, no entanto, não é uma tarefa trivial. O problema surge porque o objeto gerenciador de script está armazenado na memória do espaço computaci- onal que está no topo da hierarquia. As regras de remontagem são aplicadas no contexto de um espaço computacional subordinado e que, portanto, não pode escrever nas células dos espaços superordinados. A solução encontrada para o problema é a seguinte. O construtor da classe um Script cria thread que permanecerá executando no contexto do espaço computacional do topo da hierarquia. Quando algum espaço computacional subordinado necessitar atualizar alguma regra de transformação, basta que ele se comunique com este thread, passando-lhe os dados necessários para a atualização. Para fazer com que este processo funcione, o construtor porta de comunicação e inicia um thread init (Module) cria uma que aguarda dados serem recebidos através desta porta de comunicação e, quando os recebe, baseia-se neles para atualizar a regra de transformação em questão. 76 Para facilitar a comunicação com o thread que atualiza as regras de transformação, uma função é criada e armazenada no atributo updateRuleProc . Esta função, quando chamada, grava as informações recebidas na porta de comunicação e aguarda que o outro thread atualize a regra de transformação especicada. fazendo-se uso do método O acesso a esta função é feito updateRule (Rule). Apesar de a solução para o problema da atualização de regras ser um pouco complexa, para o utilizador da classe Script tudo está transparante: ele apenas ordena que uma regra seja atualizada, sem preocupar-se como o objeto o fará. 77 6 Considerações nais Oz nasceu e permanece no meio acadêmico. Várias linguagens e modelos de programação, alguns inclusive multiparadigma, não deixaram os artigos para tornar-se realidade. Não é o que acontece com Oz: há vários anos existe uma implementação da linguagem, chamada Mozart. A implementação Mozart é totalmente funcional, robusta e conta com uma biblioteca extensa. Desde sua concepção, Oz passou por grandes mudanças e melhorias. O ponto positivo das mudanças é que a linguagem não cou estagnada; muito pelo contrário, esteve sempre em desenvolvimento. O ponto negativo das mudanças é que as publicações mais antigas sobre Oz tem utilidade um pouco reduzida, pois tratavam da linguagem em um estado anterior de evolução. Quanto ao chatterbot ELIZA, este é tecnicamente simples, com apenas alguns con- ceitos auxiliares e sem empecilhos para a implementação. Mesmo com esta simplicidade, ELIZA impressionou e impressiona pela conversa que consegue manter, especialmente quando o interlocutor parceiro de ELIZA não sabe que fala com uma máquina. O interlocutor que sabe que está falando com um programa de computador não se demora para encontrar falhas. A conversa apresentada por Weizenbaum em [Weizenbaum 1966], reproduzida na Seção 4.1, mostra bem isso: a pessoa que conversa com ELIZA parece despreocupada em desmascarar um computador. ELIZA tem algumas limitações óbvias, por exemplo: o mecanismo de memória poderia ser facilmente expandido; o comportamento repetitivo de ELIZA poderia ser menos chamativo se um pouco de indeterminismo fosse incluído na geração das respostas. Estas e outras limitações não se encontram mais nos chatterbots atuais, o que faz de ELIZA um candidato muito fraco a ser utilizado em alguma aplicação prática. ELIZA constitui, no entanto, um bom problema para demonstrar as capacidades de Oz. Ao longo da implementação de ELIZA, a linguagem Oz revelou-se verdadeiramente multiparadigma. O único dos paradigmas principais disponíveis em Oz que não está 78 presente na implementação foi o paradigma de programação por restrições. Os seguintes paradigmas podem ser identicados na implementação de ELIZA: • Programação imperativa. imperativo, como os Ainda que as estruturas de iteração típicas do paradigma loops for e while , não foram usadas nenhuma vez, em algumas partes há a presença do estado, tão característico deste paradigma. Há estado no mecanismo de memória de ELIZA, que é alterado ao longo do tempo, e também nas regras de transformação de ELIZA. • Programação funcional. Este paradigma foi utilizado tanto na organização de pe- quenos processos (composição de funções) quanto na elaboração interna das funções. A parte de pré-processamento da entrada do usuário é um bom exemplo disso: o corpo da função consiste numa cadeia de quatro funções, e cada uma delas utiliza, para realizar sua tarefa, funções de ordem superior, recursão ou ambos. • Programação em lógica. O paradigma de programação em lógica foi utilizado na aplicação das regras de decomposição. Nesta parte da implementação, o casamento de padrões é feito descrevendo-se as condições para que o casamento dê certo, mas sem a preocupação de evitar falhas de unicação o processo de backtracking cuida delas. • Programação orientada a objetos. Este paradigma foi usado para estruturar o pro- grama num nível elevado de abstração. No baixo nível os três paradigmas mencionados acima predominam amplamente, mas quando se trata de agrupar funcionalidades, os objetos são usados. O sistema como um todo é composto de cinco classes: TextUserInterface, Screen, Eliza, Brain e Script. A idéia por trás das linguagens multiparadigma é simples: permitir ao programador escolher o paradigma de programação quando ele for resolver um problema, ao invés de forçá-lo a sempre utilizar o mesmo paradigma para todo tipo de problema. Mas por que as linguagens multiparadigma não estão sendo utilizadas em larga escala, se trazem para o programador esta liberdade? Enumero aqui alguns fatores que contribuem para esta falta de popularidade: • Antes de surgirem as linguagens multiparadigma, foi necessário que surgissem os paradigmas para que o multi zesse algum sentido. Isto tem duas conseqüências: 79 Enquanto os paradigmas de programação surgiam, eles foram muito pesquisados e linguagens representantes destes paradigmas foram criadas. Os paradigmas foram conquistando programadores ao longo deste tempo. Houve um tempo até que se pudesse pensar em linguagens multiparadigmas, ou seja, as linguagens multiparadigmas são bem mais recentes que as tradicionais. • O processo de aprender uma nova linguagem de programação é tipicamente difícil. Este processo torna-se mais árduo quando a nova linguagem de programação pertence a um paradigma que o programador desconhecia. Mais penoso ainda quando a nova linguagem o habilita a programar em vários paradigmas que ele desconhece. • O paradigma de programação imperativo mesclado com a programação orientada a objetos domina a indústria de software. A indústria utiliza várias linguagens, mas poucos paradigmas. Mas esta situação de segundo plano não enfraquece os méritos das linguagens multiparadigma. Estas linguagens realmente presenteiam o programador com o direito de escolha. Um programador que opte por escrever sempre da maneira que ele julgue ser a mais elegante encontra nas linguagens multiparadigma um meio para atingir seu objetivo. A experiência de Oz mostra que um trecho de programa pode não estar escrito claramente em um determinado paradigma. Há casos em que qualquer classicação de um código como sendo de um único paradigma será falha. Nesta situação, os paradigmas estão verdadeiramente integrados e talvez este seja um caminho para as linguagens multiparadigma: unicar os paradigmas de programação de tal forma que, ao programar, não se pense em um determinado paradigma, mas se utilize as características dos paradigmas de maneira mais livre. A implementação de ELIZA foi beneciada por ser escrita em uma linguagem exível como Oz. chatterbot Cada parte de ELIZA pôde ser escrita de maneira que casse sucinta. O executa velozmente, mesmo nas partes em que a programação em lógica foi adotada. Por m, cada problema resolvido na implementação incorporou a elegância dos paradigmas em que foi escrito. Referências 80 [ALICE Bot]ALICE Bot. Disponível em: <www.alicebot.org>. [Antoy e Hanus]ANTOY, S.; HANUS, M. Curry: A Tutorial Introduction. Disponível em: <http://www.informatik.uni-kiel.de/ curry/tutorial/tutorial.pdf>. [Bickmore 1999]BICKMORE, T. W. Social intelligence in conversational computer agents. Prosseminar Conceptual Analysis of Thesis Area. Gesture and Narrative Language, 1999. [Budd 1994]BUDD, T. A. Multiparadigm Programming in Leda. Boston, MA, USA: Addison-Wesley, 1994. ISBN 0201820803. [Colby 1975]COLBY, K. A computer simulation of paranoid processes. cial Intelligence, 1975. [Henz 1997]HENZ, M. Objects in Oz. Journal of Arti- Tese (Doutorado) Universität des Saarlandes, Fachbereich Informatik, Saarbrücken, Germany, jun. 1997. [Henz, Smolka e Würtz 1993]HENZ, M.; SMOLKA, G.; WÜRTZ, J. Oz a program- Proceedings of the Thirteenth International Joint Conference on Articial Intelligence (IJCAI-93). Chambéry, ming language for multi-agent systems. In: BAJCSY, R. (Ed.). France: Morgan Kaufmann publishers Inc.: San Mateo, CA, USA, 1993. p. 404409. [Houaiss 2003]HOUAISS, A. (Ed.). Dicionário inglês-português. [S.l.]: [Hutchens 1996]HUTCHENS, J. L. How to Pass The Turing Test by Cheating. [S.l.], abr. Record, 2003. 1996. [Kalaiyarasi, Parthasarathi e Geetha 2003]KALAIYARASI, T.; PARTHASARATHI, R.; GEETHA, T. V. Poongkuzhali - an intelligent tamil chatterbot. In: INTERNET 2003 CONFERENCE. [S.l.: [Laven 2005]LAVEN, S. nível em: SIXTH TAMIL s.n.], 2003. The Simon Laven Page. 2005. Acessado em 13/08/2005. Dispo- <http://www.simonlaven.com/>. [Leonhardt 2003]LEONHARDT, M. D. Elektra: Um chatterbot para uso em ambiente educacional. In: II Ciclo de Palestras sobre Novas Tecnologias na Educação. [S.l.: s.n.], 2003. [Loebner Prize]LOEBNER Prize. Disponível em: <http://www.loebner.net/Prizef/loebnerprize.html [Mauldin 1994]MAULDIN, M. Tinymuds, and the turing test: Entering the loebner prize competition. In: . [S.l.: s.n.], 1994. [Müller e Müller 1997]MÜLLER, T.; MÜLLER, M. Finite set constraints in Oz. Technische Universität München, p. 104115, 1719 set. 1997. [Neto et al. 2004]NETO, A. F. et al. Chatterbot em AIML para o Curso de Ciência da Computação. 2004. 81 [Paradiso e L'Abbate 2001]PARADISO, A.; L'ABBATE, M. A model for the generation Proceedings of the Workshop on Multimodal Communication and Context in Embodied Agents. and combination of emotional expressions. In: PELACHAUD; POGGI, I. (Ed.). [S.l.: s.n.], 2001. [Roy et al. 2003]ROY, P. V. et al. Logic programming in the context of multiparadigm programming: the Oz experience. Theory and Practice of Logic Programming, Cam- bridge University Press, Cambridge, v. 3, n. 6, p. 717763, 2003. [Schulte 2002]SCHULTE, C. Programming Constraint Services. Berlin, Germany: Springer-Verlag, 2002. (Lecture Notes in Articial Intelligence, v. 2302). Disponível em: <http://link.springer.de/link/service/series/0558/tocs/t2302.htm>. Finite Domain Constraint Programming in Oz: A Tutorial. 1998. Disponível em: <http://www.mozart- [Schulte, Smolka e Würtz 1998]SCHULTE, C.; SMOLKA, G.; WÜRTZ, J. oz.org/documentation/fdt/>. [Sganderla, Ferrari e Geyer 2003]SGANDERLA, R. B.; FERRARI, D. N.; GEYER, C. F. R. Bonobot: Um chatterbot para interação com usuários em um sistema tutor inteligente. In: XIV Simpósio Brasileiro de Informática na Educação. [S.l.: [Smolka 1995]SMOLKA, G. The Oz programming model. In: Computer Science Today: Recent Trends and Developments. s.n.], 2003. LEEWEN, J. v. (Ed.). Berlin: Springer-Verlag, 1995, (Lecture Notes in Computer Science, v. 1000). p. 324343. ISBN 3-540-60105-8. [Smolka, Henz e Würtz 1995]SMOLKA, G.; HENZ, M.; WÜRTZ, J. Object-oriented concurrent constraint programming in Oz. In: HENTENRYCK, P. van; SARASWAT, V. (Ed.). Principles and Practice of Constraint Programming. [S.l.]: The MIT Press, 1995. cap. 2, p. 2948. [Sterling e Shapiro 1994]STERLING, L.; SHAPIRO, E. gramming techniques. Second. Cambridge: The Art of Prolog: advanced pro- MIT Press, 1994. ISBN 0-262-19338-8. [The American Heritage Dictionary of the English Language 2000]THE American Heritage Dictionary of the English Language. [S.l.]: Houghton Miin Company, 2000. [Vrajitoru 2004]VRAJITORU, D. Evolutionary sentence building for chatterbots. In: IASTED International Conference on Articial Intelligence and Applications. [S.l.: The s.n.], 2004. [Weizenbaum 1966]WEIZENBAUM, J. ELIZA A computer program for the study of natural language communication between man and machine. ACM, v. 9, n. 1, p. 3644, jan. 1966. Communications of the A Survey on Finite Domain Programming in Oz. 1996. Disponível em: <citeseer.ist.psu.edu/153317.html>. [Würtz e Müller 1996]WÜRTZ, J.; MÜLLER, T. 82 APÊNDICE A -- Doctor.oz functor export welcome : Welcome groups : Groups substs : Substitutions memory : Memory comments : Comments keywords : Keywords define % % The welcome message. % Welcome = "HOW DO YOU DO. PLEASE STATE YOUR PROBLEM" % % Simple substitutions % Substitutions = [ subst ('from' : "DONT" to : "DON'T") subst ('from' : "CANT" to : "CAN'T") subst ('from' : "WONT" to : "WON'T") subst ('from' : "DREAMED" to : "DREAMT") subst ('from' : "DREAMS" to : "DREAM") subst ('from' : "AM" to : "ARE") 83 subst ('from' : "YOUR" to : "MY") subst ('from' : "I" to : "YOU") subst ('from' : "YOU" to : "I") subst ('from' : "MY" to : "YOUR") subst ('from' : "WERE" to : "WAS") subst ('from' : "ME" to : "YOU") subst ('from' : "MYSELF" to : "YOURSELF") subst ('from' : "YOURSELF" to : "MYSELF") subst ('from' : "YOU'RE" to : "I'M") subst ('from' : "I'M" to : "YOU'RE") ] % % Groups of words % Groups = [ group (name : belief words : ["FEEL" "THINK" "BELIEVE" "WISH"]) group (name : family words : ["MOTHER" "MOM" "DAD" "FATHER" "SISTER" "BROTHER" "WIFE" "CHILDREN"]) ] % % Keywords % Keywords = [ keyword (text : "SORRY" 84 rank : 0 rules : [rule (decomp : [0] reasm : [["PLEASE DON'T APOLOGIZE"] ["APOLOGIES ARE NOT NECESSARY"] ["WHAT FEELINGS DO YOU HAVE WHEN YOU APOLOGIZE"] ["I'VE TOLD YOU THAT APOLOGIES ARE NOT REQUIRED"]])]) keyword (text : "REMEMBER" rank : 5 rules : [rule (decomp : [0 "YOU" "REMEMBER" 1] reasm : [["DO YOU OFTEN THINK OF" 1] ["DOES THINKING OF" 1 "BRING ANYTHING ELSE TO MIND"] ["WHAT ELSE DO YOU REMEMBER"] ["WHY DO YOU REMEMBER" 1 "JUST NOW"] ["WHAT IN THE PRESENT SITUATION REMINDS YOU OF" 1] ["WHAT IS THE CONNECTION BETWEEN ME AND" 1]]) rule (decomp : [0 "DO" "I" "REMEMBER" 1] reasm : [["DID YOU THINK I WOULD FORGET" 1] ["WHY DO YOU THINK I SHOULD RECALL" 1 "NOW"] ["WHAT ABOUT" 1] equals (keyword : "WHAT") ["YOU MENTIONED" 1]]) rule (decomp : [1] reasm : [newkey ])]) keyword (text : "IF" rank : 3 rules : [rule (decomp : [0 "IF" 1] reasm : [["DO YOU THINK ITS LIKELY THAT" 1] 85 ["DO YOU WISH THAT" 1] ["WHAT DO YOU THINK ABOUT" 1] ["REALLY, IF" 1]])]) keyword (text : "DREAMT" rank : 4 rules : [rule (decomp : [0 "YOU" "DREAMT" 1] reasm : [["REALLY," 1] ["HAVE YOU EVER FANTASIED" 1 "WHILE YOU WERE AWAKE"] ["HAVE YOU DREAMT" 1 "BEFORE"] equals (keyword : "DREAM") newkey ])]) keyword (text : "DREAM" rank : 3 rules : [rule (decomp : [0] reasm : [["WHAT DOES THAT DREAM SUGGEST TO YOU"] ["DO YOU DREAM OFTEN"] ["WHAT PERSONS APPEAR IN YOUR DREAMS"] ["DON'T YOU BELIEVE THAT DREAM HAS SOMETHING TO DO WITH YOUR PROBLEM"] newkey ])]) keyword (text : "PERHAPS" rank : 0 rules : [rule (decomp : [0] reasm : [["YOU DON'T SEEM QUITE CERTAIN"] ["WHY THE UNCERTAIN TONE"] ["CAN'T YOU BE MORE POSITIVE"] ["YOU AREN'T SURE"] ["DON'T YOU KNOW"]])]) keyword (text : "NAME" rank : 15 86 rules : [rule (decomp : [0] reasm : [["I AM NOT INTERESTED IN NAMES"] ["I'VE TOLD YOU BEFORE, I DON'T CARE ABOUT NAMES - PLEASE CONTINUE"]])]) keyword (text : "XFREMD" rank : 0 rules : [rule (decomp : [0] reasm : [["I AM SORRY, I SPEAK ONLY ENGLISH"""]])]) keyword (text : "HELLO" rank : 0 rules : [rule (decomp : [0] reasm : [["HOW DO YOU DO. PLEASE STATE YOUR PROBLEM"]])]) keyword (text : "COMPUTER" rank : 50 rules : [rule (decomp : [0] reasm : [["DO COMPUTERS WORRY YOU"] ["WHY DO YOU MENTION COMPUTERS"] ["WHAT DO YOU THINK MACHINES HAVE TO DO WITH YOUR PROBLEM"] ["DON'T YOU THINK COMPUTERS CAN HELP PEOPLE"] ["WHAT ABOUT MACHINES WORRIES YOU"] ["WHAT DO YOU THINK ABOUT MACHINES"]])]) keyword (text : "ARE" rank : 0 rules : [rule (decomp : [0 "ARE" "YOU" 1] reasm : [["DO YOU BELIEVE YOU ARE" 1] ["WOULD YOU WANT TO BE" 1] ["YOU WISH I WOULD TELL YOU YOU ARE" 1] 87 ["WHAT WOULD IT MEAN IF YOU WERE" 1] equals (keyword : "WHAT")]) rule (decomp : [0 "ARE" "I" 1] reasm : [["WHY ARE YOU INTERESTED IN WHETHER I AM" 1 "OR NOT"] ["WOULD YOU PREFER IF I WEREN'T" 1] ["PERHAPS I AM" 1 "IN YOUR FANTASIES"] ["DO YOU SOMETIMES THINK I AM" 1] equals (keyword : "WHAT")]) rule (decomp : [0 "ARE" 1] reasm : [["DID YOU THINK THEY MIGHT NOT BE" 1] ["WOULD YOU LIKE IT IF THEY WERE NOT" 1] ["WHAT IF THEY WERE NOT" 1] ["POSSIBLY THEY ARE" 1]]) % The rule below will never be used... rule (decomp : [0] reasm : [["WHY DO YOU SAY 'AM'"] ["I DON'T UNDERSTAND THAT"]])]) keyword (text : "MY" rank : 0 rules : [rule (decomp : [0 "MY" 1] reasm : [["WHY ARE YOU CONCERNED OVER MY" 1] ["WHAT ABOUT YOUR OWN" 1] ["ARE YOU WORRIED ABOUT SOMEONE ELSES" 1] ["REALLY, MY" 1]])]) keyword (text : "WAS" rank : 2 rules : [rule (decomp : [0 "WAS" "YOU" 1] reasm : [["WHAT IF YOU WERE" 1] ["DO YOU THINK YOU WERE" 1] ["WERE YOU" 1] ["WHAT WOULD IT MEAN IF WERE" 1] 88 ["WHAT DOES '" 1 "' SUGGEST TO YOU"] equals (keyword : "WHAT")]) rule (decomp : [0 "YOU" "WAS" 1] reasm : [["WERE YOU REALLY"] ["WHY DO YOU TELL ME YOU WERE" 1 "NOW"] ["PERHAPS I ALREADY KNEW YOU WERE" 1]]) rule (decomp : [0 "WAS" "I" 1] reasm : [["WOULD YOU LIKE TO BELIEVE I WAS" 1] ["WHAT SUGGESTS THAT I WAS" 1] ["WHAT DO YOU THINK"] ["PERHAPS I WAS" 1] ["WAHT IF I HAD BEEN" 1]]) rule (decomp : [0] reasm : [newkey ])]) keyword (text : "YOU" rank : 0 rules : [rule (decomp : [0 "YOU" alt (num : 0 words : ["WANT" "NEED"]) 1] reasm : [["WHAT WOULD IT MEAN TO YOU IF YOU GOT" 1] ["WHY DO YOU WANT" 1] ["SUPPOSE YOU GOT" 1 "SOON"] ["WHAT IF YOU NEVER GOT" 1] ["WHAT WOULD GETTING" 1 "MEAN TO YOU"] ["WHAT DOES WANTING" 1 "HAS TO DO WITH THIS DISCUSSION"]]) rule (decomp : [0 "YOU" "ARE" 0 alt (num : 1 words : ["SAD" "UNHAPPY" "DEPRESSED" "SICK"]) 0] reasm : [["I AM SORRY TO HEAR YOU ARE" 1] ["DO YOU THINK COMING HERE WILL HELP YOU NOT TO BE" 1] ["I'M SURE ITS NOT PLEASANT TO BE" 1] ["CAN YOU EXPLAIN WHAT MADE YOU" 1]]) rule (decomp : [0 "YOU" "ARE" 0 alt (num : 1 words : 89 "HAPPY" "ELATED" "GLAD" "BETTER") 0] reasm : [["HOW HAVE I HELPED YOU TO BE" 1] ["HAS YOUR TREATMENT MADE YOU" 1] ["WHAT MAKES YOU" 1 "JUST NOW"] ["CAN YOU EXPLAIN WHY YOU ARE SUDDENLY" 1]]) rule (decomp : [0 "YOU" "WAS" 0] reasm : [equals (keyword : "WAS")]) rule (decomp : [0 "YOU" group (num : 0 name : belief ) "YOU" 1] reasm : [["DO YOU REALLY THINK SO"] ["BUT YOU ARE NOT SURE YOU" 1] ["DO YOU REALLY DOUBT YOU" 1]]) rule (decomp : [0 "YOU" group (num : 0 name : belief ) 0 "I" 0] reasm : [equals (keyword : "I")]) rule (decomp : [0 "YOU" "ARE" 1] reasm : [["IS IT BECAUSE YOU ARE" 1 "THAT YOU CAME TO ME"] ["HOW LONG HAVE YOU BEEN" 1] ["DO YOU BELIEVE IT NORMAL TO BE" 1] ["DO YOU ENJOY BEING" 1]]) rule (decomp : [0 "YOU" alt (num : 0 words : ["CAN'T" "CANNOT"]) 1] reasm : [["HOW DO YOU KNOW YOU CAN'T" 1] ["HAVE YOU TRIED"] ["PERHAPS YOU COULD" 1 "NOW"] ["DO YOU REALLY WANT TO BE ABLE TO" 1]]) rule (decomp : [0 "YOU" "DON'T" 1] reasm : [["DON'T YOU REALLY" 1] ["WHY DON'T YOU" 1] ["DO YOU WISH TO BE ABLE TO" 1] ["DOES THAT TROUBLE YOU"]]) rule (decomp : [0 "YOU" "FEEL" 1] reasm : [["TELL ME MORE ABOUT SUCH FEELINGS"] 90 ["DO YOU OFTEN FEEL" 1] ["DO YOU ENJOY FEELING" 1] ["OF WHAT DOES FEELING" 1 "REMIND YOU"]]) rule (decomp : [0 "YOU" 1 "I" 0] reasm : [["PERHAPS IN YOUR FANTASY WE" 1 "EACH OTHER"] ["DO YOU WISH TO" 1 "ME"] ["YOU SEEM TO NEED TO" 1 "ME"] ["DO YOU" 1 "ANYONE ELSE"]]) rule (decomp : [1] reasm : [["YOU SAY" 1] ["CAN YOU ELABORATE ON THAT"] ["DO YOU SAY" 1 "FOR SOME SPECIAL REASON"] ["THAT'S QUITE INTERESTING"]])]) keyword (text : "I" rank : 0 rules : [rule (decomp : [0 "I" "REMIND" "YOU" "OF" 0] reasm : [equals (keyword : "DIT")]) rule (decomp : [0 "I" "ARE" 1] reasm : [["WHAT MAKES YOU THINK I AM" 1] ["DOES IT PLEASE YOU TO BELIEVE I AM" 1] ["DO YOU SOMETIMES WISH YOU WERE" 1] ["PERHAPS YOU WOULD LIKE TO BE" 1]]) rule (decomp : [0 "I" 1 "YOU"] reasm : [["WHY DO YOU THINK I" 1 "YOU"] ["YOU LIKE TO THINK I" 1 "YOU - DON'T YOU"] ["WHAT MAKES YOU THINK I" 1 "YOU"] ["REALLY, I" 1 "YOU"] ["DO YOU WISH TO BELIEVE I" 1 "YOU"] ["SUPPOSE I DID" 1 "YOU - WHAT WOULD THAT MEAN"] 91 ["DOES SOMEONE ELSE BELIEVE I" 1 "YOU"]]) rule (decomp : [0 "I" 1] reasm : [["WE WERE DISCUSSING YOU - NOT ME"] ["OH, I" 1] ["YOU'RE NOT REALLY TALKING ABOUT ME ARE YOU"] ["WHAT ARE YOUR FEELINGS NOW"]])]) keyword (text : "YES" rank : 0 rules : [rule (decomp : [0] reasm : [["YOU SEEM QUITE POSITIVE"] ["YOU ARE SURE"] ["I SEE"] ["I UNDERSTAND"]])]) keyword (text : "NO" rank : 0 rules : [rule (decomp : [0] reasm : [["ARE YOU SAYING 'NO' JUST TO BE NEGATIVE"] ["YOU ARE BEING A BIT NEGATIVE"] ["WHY NOT"] ["WHY 'NO'"]])]) keyword (text : "YOUR" rank : 2 rules : [rule (decomp : [0 "YOUR" 0 group (num : 1 name : family ) 2] reasm : [["TELL ME MORE ABOUT YOUR FAMILY"] ["WHO ELSE IN YOUR FAMILY" 2] ["YOUR" 1] ["WHAT ELSE COMES TO MIND WHEN YOU THINK OF YOUR" 1]]) 92 rule (decomp : [0 "YOUR" 1] reasm : [["YOUR" 1] ["WHY DO YOU SAY YOUR" 1] ["DOES THAT SUGGEST ANYTHING ELSE WHICH BELONGS TO YOU"] ["IS IT IMPORTANT TO YOU THAT YOUR" 1]])]) keyword (text : "CAN" rank : 0 rules : [rule (decomp : [0 "CAN" "I" 1] reasm : [["YOU BELIEVE I" 1 "DON'T YOU"] equals (keyword : "WHAT") ["YOU WANT ME TO BE ABLE TO" 1] ["PERHAPS YOU WOULD LIKE TO BE ABLE TO" 1 "YOURSELF"]]) rule (decomp : [0 "CAN" "YOU" 1] reasm : [["WHETHER OR NOT YOU CAN" 1 "DEPENDS ON YOU MORE THAN ON ME"] ["DO YOU WANT TO BE ABLE TO" 1] ["PERHAPS YOU DON'T WANT TO" 1] equals (keyword : "WHAT")])]) keyword (text : "WHAT" rank : 0 rules : [rule (decomp : [0] reasm : [["WHY DO YOU ASK"] ["DOES THAT QUESTION INTEREST YOU"] ["WHAT IS IT YOU REALLY WANT TO KNOW"] ["ARE SUCH QUESTIONS MUCH ON YOUR MIND"] ["WHAT ANSWER WOULD PLEASE YOU MOST"] ["WHAT DO YOU THINK"] ["WHAT COMES TO YOUR MIND WHEN YOU ASK THAT"] ["HAVE YOU ASKED SUCH QUESTION BEFORE"] 93 ["HAVE YOU ASKED ANYONE ELSE"]])]) keyword (text : "BECAUSE" rank : 0 rules : [rule (decomp : [0] reasm : [["IS THAT THE REAL REASON"] ["DON'T ANY OTHER REASONS COME TO MIND"] ["DOES THAT REASON SEEM TO EXPLAIN ANYTHING ELSE"] ["WHAT OTHER REASONS MIGHT THERE BE"]])]) keyword (text : "WHY" rank : 0 rules : [rule (decomp : [0 "WHY" "DON'T" "I" 1] reasm : [["DO YOU BELIEVE I DON'T" 1] ["PERHAPS I WILL" 1 "IN GOOD TIME"] ["SHOULD YOU" 1 "YOURSELF"] ["YOU WANT ME TO" 1] equals (keyword : "WHAT")]) rule (decomp : [0 "WHY" "CAN'T" "YOU" 1] reasm : [["DO YOU THINK YOU SHOULD BE ABLE TO" 1] ["DO YOU WANT TO BE ABLE TO" 1] ["DO YOU BELIEVE THIS WILL HELP YOU TO" 1] ["HAVE YOU ANY IDEA WHY YOUU CAN'T" 1] equals (keyword : "WHAT")]) rule (equals : "WHAT")]) keyword (text : "EVERYONE" rank : 2 rules : [rule (decomp : [0 alt (num : 1 words : ["EVERYONE" "EVERYBODY" "NOBODY" "NOONE"]) 0] reasm : [["REALLY," 1] ["SURELY NOT" 1] 94 ["CAN YOU THINK OF ANYONE IN PARTICULAR"] ["WHO, FOR EXAMPLE"] ["YOU ARE THINKING OF A VERY SPECIAL PERSON"] ["WHO, MAY I ASK"] ["SOMEONE SPECIAL, PERHAPS"] ["YOU HAVE A PARTICULAR REASON IN MIND, DON'T YOU"] ["WHO DO YOU THINK YOU'RE TALKING ABOUT"]])]) keyword (text : "ALWAYS" rank : 1 rules : [rule (decomp : [0] reasm : [["CAN YOU THINK OF A SPECIFIC EXAMPLE"] ["WHEN"] ["WHAT INCIDENT ARE YOU THINKING OF"] ["REALLY, ALWAYS"]])]) keyword (text : "LIKE" rank : 10 rules : [rule (decomp : [0 alt (num : 0 words : ["AM" "IS" "ARE"]) 0 "LIKE" 0] reasm : [equals (keyword : "DIT")]) rule (decomp : [0] reasm : [newkey ])]) keyword (text : "DIT" rank : 0 rules : [rule (decomp : [0] reasm : [["IN WHAT WAY"] ["WHAT RESEMBLANCE DO YOU SEE"] ["WHAT DOES THAT SIMILARITY SUGGEST TO YOU"] 95 ["WHAT OTHER CONNECTIONS DO YOU SEE"] ["WHAT DO YOU SUPPOSE THAT RESEMBLANCE MEANS"] ["WHAT IS THE CONNECTION, DO YOU SUPPOSE"] ["COULD THERE REALLY BE SOME CONNECTION"] ["HOW"]])]) keyword (text : "HOW" rank : 0 rules : [rule (equals : "WHAT")]) keyword (text : "WHEN" rank : 0 rules : [rule (equals : "WHAT")]) keyword (text : "ALIKE" rank : 10 rules : [rule (equals : "DIT")]) keyword (text : "SAME" rank : 10 rules : [rule (equals : "DIT")]) keyword (text : "CERTAINLY" rank : 0 rules : [rule (equals : "YES")]) keyword (text : "MAYBE" rank : 0 rules : [rule (equals : "PERHAPS")]) keyword (text : "DEUTSCH" rank : 0 96 rules : [rule (equals : "XFREMD")]) keyword (text : "FRANCAIS" rank : 0 rules : [rule (equals : "XFREMD")]) keyword (text : "ITALIANO" rank : 0 rules : [rule (equals : "XFREMD")]) keyword (text : "ESPANOL" rank : 0 rules : [rule (equals : "XFREMD")]) keyword (text : "MACHINE" rank : 50 rules : [rule (equals : "COMPUTER")]) keyword (text : "MACHINES" rank : 50 rules : [rule (equals : "COMPUTER")]) keyword (text : "COMPUTERS" rank : 50 rules : [rule (equals : "COMPUTER")]) keyword (text : "EVERYBODY" rank : 2 rules : [rule (equals : "EVERYONE")]) keyword (text : "NOBODY" rank : 2 rules : [rule (equals : "EVERYONE")]) keyword (text : "NOONE" 97 rank : 2 rules : [rule (equals : "EVERYONE")]) keyword (text : "I'M" rank : 0 rules : [rule (decomp : [0 "I'M" 1] reasm : pre (reasm : ["I" "ARE" 1] keyword : "I"))]) keyword (text : "YOU'RE" rank : 0 rules : [rule (decomp : [0 "YOU'RE" 1] reasm : [pre (reasm : ["YOU" "ARE" 1] keyword : "YOU")])]) ] % % Memory % Memory = memory (keyword : "YOUR" transfs : [ transf (decomp : [0 "YOUR" 1] reasm : ["LETS DISCUSS FURTHER WHY YOUR" 1]) transf (decomp : [0 "YOUR" 1] reasm : ["EARLIER YOU SAID YOUR" 1]) transf (decomp : [0 "YOUR" 1] reasm : ["BUT YOUR" 1]) transf (decomp : [0 "YOUR" 1] reasm : ["DOES THAT HAVE ANYTHING TO DO WITH THE FACT THAT YOUR" 1])]) % 98 % Context-free comments % Comments = [ "I AM NOT SURE I UNDERSTAND YOU FULLY." "PLEASE GO ON." "WHAT DOES THAT SUGGEST TO YOU" "DO YOU FEEL STRONGLY ABOUT DISCUSSING SUCH THINGS" ] end 99 APÊNDICE B -- Eliza.oz functor import System Search OS export eliza : Eliza define class Eliza attr brain meth init (Module) @brain ={New Brain init (Module)} end meth chat (UserInterface) proc {ChatLoop} 100 case {UserInterface readUserInput ($)} of false then skip [] Input then {UserInterface writeAnswer ({@brain makeAnswer (Input $)})} {ChatLoop} end end in {UserInterface writeAnswer ({@brain getWelcome ($)})} {ChatLoop} end end class Brain attr script memory meth init (Module) @script ={New Script init (Module)} @memory =nil skip end 101 meth getWelcome ($) {@script getWelcome ($)} end meth makeAnswer (Input Answer) fun {Preprocess UserInput} fun {Uppercase String} {Map String Char.toUpper } end fun {NormalizeSpaces String} {FoldL String fun {$ String Ch} if String == nil then [Ch] else if {And {Char.isSpace Ch} {Char.isSpace {List.last String}}} then String else {Append String [Ch]} end end end nil } 102 end fun {SplitIntoPhrases String} Phrase Rest in {List.takeDropWhile {List.takeDropWhile String fun {$ C} {Not {Char.isAlpha C}} end _ $} fun {$ C} {Or {Not {Char.isPunct C}} (C == &')} end Phrase Rest} if Phrase \= nil then Phrase|{SplitIntoPhrases Rest} else nil end end fun {SplitIntoWords Phrases} {Map Phrases fun {$ L} {String.tokens L & } end} end in 103 {SplitIntoWords {SplitIntoPhrases {NormalizeSpaces {Uppercase UserInput}}}} end fun {MakeAnswerFromPhrases Phrases} fun {ApplySimpleSubstitutions Phrase} fun {Substitute Word} NewWord={@script getSubstitution (Word $)} in if NewWord \= nil then NewWord else Word end end in {Map Phrase Substitute} end fun {GetKeywords Phrase} case Phrase of nil then nil [] X|Xs then Y in Y={@script getKeyword (X $)} 104 if Y==false then {GetKeywords Xs} else Y|{GetKeywords Xs} end end end fun {SortKeywords Keywords} fun {Compare Stack Keyword} case Stack of S|_ then if Keyword.rank > S.rank then {Append [Keyword] Stack} else {Append Stack [Keyword]} end [] nil then [Keyword] end end in {FoldL Keywords Compare nil } end proc {Decompose Input Pattern Map} 105 proc {SubMatch Input Result Rest} Ir R Rr in choice R|Rr=Result R|Ir=Input {SubMatch Ir Rr Rest} [] Result=nil Rest=Input end end P Pr I Ir Q N Alt Group in choice P|Pr=Pattern choice P=0 {Decompose {SubMatch Input _ $} Pr Map} [] {IsNumber P}=true P>0=true {Dictionary.put Map P Q} {Decompose {SubMatch Input Q $} Pr Map} [] P=alt (num : N words : Alt) I|Ir=Input {Member I Alt}=true {Dictionary.put Map N [I]} {Decompose Ir Pr Map} [] P=group (num : N name : Group) I|Ir=Input {Member I {@script getGroup (Group $)}}=true {Dictionary.put Map N [I]} {Decompose Ir Pr Map} [] P|Ir=Input {Decompose Ir Pr Map} 106 end [] Pattern=nil Input=nil end end fun {Reassemble Phrase Substs} case Phrase of nil then nil [] P|Pr then case {IsNumber P} of true then {Append {Dictionary.get Substs P} {Reassemble Pr Substs $}} [] false then P|{Reassemble Pr Substs $} end end end proc {ApplyRules Input SortedKeywords Answer} proc {MakeFromPatterns Input Patterns Answer} Pr P S Phrase in P|Pr=Patterns case P of rule (equals : K next : _) then Answer=equals (K) [] rule (decomp : Q next : C reasm : A) then choice S={Dictionary.new } {Decompose Input Q S} 107 Phrase={List.nth A @C} case Phrase of pre (reasm : Reasm keyword : Keyword) then Answer=pre (phrase : {Reassemble Reasm S $} keyword : Keyword) [] newkey () then Answer=newkey () [] equals (keyword : K) then Answer=equals (K) else Answer=answer ({Reassemble Phrase S $}) {@script updateRule (P)} end [] {MakeFromPatterns Input Pr Answer} end end end P Pr Patterns T proc {PrintKeywords Ks} case Ks of K|O then {System.printError K.text #"\n"} {PrintKeywords O} else skip end end in P|Pr=SortedKeywords P.rules =Patterns T={MakeFromPatterns Input Patterns} case T of newkey () then 108 {ApplyRules Input Pr Answer} [] pre (phrase : Phrase keyword : Keyword) then {ApplyRules Phrase {@script getKeyword (Keyword $)}|Pr Answer} [] equals (K) then {ApplyRules Input {@script getKeyword (K $)}|Pr Answer} [] answer (A) then Answer=A end end proc {MakeContextFreeComment Answer} Comments Next in comments (reasm : Comments next : Next)={@script getComments ($)} Answer=[{List.nth Comments @Next}] {@script updateRule ({@script getComments ($)})} end proc {UpdateMemory Input Keyword} proc {AddToMemory Answer} memory :={Append @memory [Answer]} end proc {DoMatch S} S={Dictionary.new } {Decompose Input Trans.decomp end S} 109 N Trans in if Keyword.text == {@script getMemory ($)}.keyword then N=(({OS.rand } mod {List.length {@script getMemory ($)}.transfs }) + 1) Trans={List.nth {@script getMemory ($)}.transfs N} case {Search.base .one proc {$ S} S={DoMatch} end} of nil then skip [] [Dic] then {AddToMemory {Reassemble Trans.reasm Dic}} end end end fun {RetrieveFromMemory} case @memory of M|Mr then memory :=Mr M [] nil then nil end end in case Phrases of P|Pr then Simple in Simple={ApplySimpleSubstitutions P} case {SortKeywords {GetKeywords Simple}} of nil then 110 {MakeAnswerFromPhrases Pr} [] Keywords then {UpdateMemory Simple {List.nth Keywords 1}} case {Search.base .one fun{$} {ApplyRules Simple Keywords} end} of nil then {MakeAnswerFromPhrases nil } [] [Answer] then Answer end end [] nil then case {RetrieveFromMemory} of nil then {MakeContextFreeComment} [] Answer then Answer end end end fun {PostProcess Answer} case Answer of L|Lr then {Append L {Append " " {PostProcess Lr}}} [] nil then nil end end in Answer={PostProcess {MakeAnswerFromPhrases {Preprocess Input}}} 111 end end class Script attr welcome groups substs memory comments keywords updateRuleProc meth init (Module) fun {StartRuleUpdater} UpdaterPort RuleList Wait proc {RuleUpdater RuleList} case RuleList of R|Rr then X Y in if @(R.next )=={Length R.reasm } then (R.next ):=1 else (R.next ):=@(R.next )+1 end Y=@Wait Wait:=X 112 Y=nil |X {RuleUpdater Rr} [] nil then skip end end in UpdaterPort={NewPort RuleList} thread {RuleUpdater RuleList} end Wait={NewCell _} proc {$ Rule} {Port.send UpdaterPort Rule} case @Wait of nil then skip else skip end end end fun {ProcessKeywords Keywords} fun {ProcessRules Rules} case Rules of R|Rr then {Adjoin R rule (next : {NewCell 1})}|{ProcessRules Rr} [] nil then nil end 113 end in case Keywords of K|Kr then keyword (text : K.text rank : K.rank rules : {ProcessRules K.rules })|{ProcessKeywords Kr} [] nil then nil end end proc {ProcessGroups Groups Map} case Groups of S|Sr then {Dictionary.put Map S.name S.words } {ProcessGroups Sr Map} [] nil then skip end end in @updateRuleProc ={StartRuleUpdater} @welcome =Module.welcome @substs =Module.substs 114 @memory =Module.memory @comments =comments (reasm : Module.comments next : {NewCell 1}) @keywords ={ProcessKeywords Module.keywords } @groups ={Dictionary.new } {ProcessGroups Module.groups @groups } end meth getWelcome ($) @welcome end meth getComments ($) @comments end meth getKeyword (Word $) fun {GetKeywordFromList Word List} case List of nil then false [] X|Xs then if X.text == Word then X else {GetKeywordFromList Word Xs} end end end in {GetKeywordFromList Word @keywords } end meth getGroup (GroupName $) {Dictionary.get @groups GroupName} end 115 meth getSubstitution (Word $) Find={List.dropWhile @substs fun {$ S} S.'from' \= Word end} in case Find of F|_ then F.'to' else nil end end meth getMemory ($) @memory end meth updateRule (Rule) {@updateRuleProc Rule} end end end 116 APÊNDICE C -- TextUserInterface.oz functor import Open System Application Module Eliza Doctor define class Screen attr stdin stdout meth init () class TextFile from Open.file Open.text end in stdin :={New TextFile init (name :stdin )} stdout :={New TextFile init (name :stdout )} end 117 meth readLine ($) fun {RemoveTrailingNewline Line} case Line of L|Lr then case L of 13 then nil [] 10 then nil else L|{RemoveTrailingNewline Lr} end else Line end end Line in Line={@stdin getS ($)} case Line of false then false else {RemoveTrailingNewline Line} end end meth write (String) {@stdout write (vs :String)} end meth writeLine (String) {self write (String)} {self write ([13 10])} end end class TextUserInterface 118 attr screen meth init () screen :={New Screen init ()} end meth readUserInput ($) proc {PrintPrompt} {@screen write ("> ")} end in {PrintPrompt} case {@screen readLine ($)} of false then false [] "quit" then false [] Line then Line end end meth writeAnswer (Answer) {@screen write ("# ")} {@screen writeLine (Answer)} 119 end meth writeLine (Line) {@screen writeLine (Line)} end end proc {Main} ArgSpec=record (help (rightmost char : &h default : false) data (single char : &d type : string default : false)) UsageString= "usage: eliza OPTION...\n"# "-h, --help Shows usage info\n"# "-d, --data FILE Use FILE as rule database\n" proc {ShowUsageAndQuit ExitCode} {System.printError UsageString} {Application.exit ExitCode} end Args DataModule ElizaChatterbot UserInterface in try Args={Application.getCmdArgs ArgSpec} catch _ then {ShowUsageAndQuit 1} end 120 if Args.1 \= nil then {ShowUsageAndQuit 1} end if Args.help then {ShowUsageAndQuit 0} end try if Args.data \= false then [DataModule]={Module.link [Args.data ]} else DataModule=Doctor end ElizaChatterbot={New Eliza.eliza init (DataModule)} UserInterface={New TextUserInterface init ()} catch _ then {System.printError "Error loading module '"#Args.data #"'.\n"} {Application.exit 2} end {UserInterface writeLine ("This is Eliza. finished.")} {ElizaChatterbot chat (UserInterface)} {Application.exit 0} Type \"quit\" when 121 end in {Main} end