Implementação do chatterbot ELIZA na linguagem multiparadigma Oz

Propaganda
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
Download