TEACH ELIZA Steve Hardy, October 1981 Bug in instructions to run eliza fixed 28 Jan 1999 This handout describes how to write a simple 'eliza like' program. ELIZA was an early AI program which embodied some knowledge about the rules of conversation. It was developed by Joseph Weizenbaum (author of 'Computer Power and Human Judgement'). Descriptions of ELIZA can be found in: Margaret Boden: 'Artificial Intelligence and Natural Man' pages 96, 106 etc (see subject index) Bertram Raphael: 'The Thinking Computer' table 6.4, page 199 CONTENTS - (Use g to access sections) -- PLAY WITH ELIZA -- PREPARATORY READING -- ASK FOR HELP IF YOU NEED IT -- CONVERSE, A FIRST EXAMPLE -- MAKING CONVERSE INTERACTIVE -- CONVERSE EXPLAINED A LINE AT A TIME -- HOW TO SWITCH BETWEEN TEACH AND VED -- WRITE YOUR OWN VERSION OF CONVERSE -- MAKING CONVERSE CONDITIONAL -- DOCTOR, A MORE COMPLICATED EXAMPLE -- DOCTOR EXPLAINED A LINE AT A TIME -- SUMMARY -- A DIAGRAMMATIC EXPLANATION OF DOCTOR -- BIG PROCEDURES CAN BE BROKEN UP INTO SMALLER COMPONENTS -- AN EXERCISE: EXTEND THE DOCTOR PROGRAM -- GUESS, A DIFFERENT SORT OF EXAMPLE -- MAKE UP YOUR OWN EXAMPLE -- BREAK BIG PROCEDURES UP INTO BITS -- INTRODUCING MATCHES -- A COMMENT ON CONDITIONS -- SOME EXERCISES USING MATCHES -- USING MATCHES TO EXTEND DOCTOR -- RESPONDTO EXPLAINED -- FURTHER READING -- PLAY WITH ELIZA ----------------------------------------------------- You can play with a mini-version of ELIZA too. If you are using VED (but not XVed) you can do the following: PRESS the ENTER key TYPE $eliza (include the dollar symbol) PRESS the RETURN key If you are using XVed, then you should type the following (without a dollar symbol) to a Unix prompt in an Xterm window: eliza If for any reason the "eliza" command has not been set up on your machine, but pop11 works, you can instead type pop11 +eliza You may find that an option to run Eliza has been set up in one of you mouse menus (e.g. middle mouse, pressed with the mouse pointer in a portion of the screen outside all windows). -- PREPARATORY READING ------------------------------------------------ Before reading the rest of this help file, you should have read TEACH VED, and TEACH VEDPOP, since you will need to create a program file of your own. TEACH VARS will also be useful. Use the LF button, or keypad 2 or the DOWN-SCREEN button to read on: -- ASK FOR HELP IF YOU NEED IT ----------------------------------------- This help file will suggest things for you to try out on POP-11 and make suggestions about creating program files. If you have difficulities, ask for help. -- CONVERSE, A FIRST EXAMPLE ------------------------------------------- A procedure definition follows, which you should put into a file called, say: 'converse.p'. To tell VED you want to create (or alter) a file of this name give an 'ENTER ved converse.p' command, that is press the ENTER key, type the 'ved converse.p' then press RETURN. If you didn't know how to do this then you didn't read VEDPOP properly. define converse(); [sorry - i am too stupid to talk] => enddefine; Once you have typed in the definition you can have a particularly dumb conversation, all you need do next is go to POP-11 (with ENTER-X) and try the new procedure, by giving the the POP-11 command: converse(); Type TEACH; to get back here. -- MAKING CONVERSE INTERACTIVE ----------------------------------------- We can make this program a bit smarter by getting it to ask some question and then use the answer to form the next bit of conversation. This is easiest if the question demands a stereotyped answer like 'yes' or, perhaps, a name. Use VED to change your procedure to: define converse(); vars name; [hello] => [what is your name] => readline() -> name; [pleased to meet you ^^name] => [well - i must go now] => enddefine; To invoke VED give an 'ENTER ved' command, that is press ENTER, type 'ved' and then press RETURN. Don't worry if you don't understand the new definition. By the way, the '^' character is at the top right of the keyboard close to the DEL key and between the '-' and '\' keys. Try the new definition by giving the command (after getting to POP-11 with ENTER-X): converse(); Then type 'TEACH;' to get back here. -- CONVERSE EXPLAINED A LINE AT A TIME --------------------------------- Look back at the definition of the procedure CONVERSE, and try to work out what it means. Then read on. Use UP-SCREEN and DOWN-SCREEN keys. Let us take the definition a line at a time and work out what it is doing. The first line: define converse(); tells the system that you want to define a new command - called 'converse'. The parentheses are necessary but for the moment we'll ignore them. The second line says: vars name; From reading TEACH VARS you will know that this tells POP-11 that 'name' is to be used as a 'variable' for storing something (in this case, it will be used to store a list). The third and fourth lines: [hello] => [what is your name] => simply tell POP-11 that it should print something. The fifth line: readline() -> name; is very curious. It tells POP-11 that at this point it is to stop and wait for the user to type in something. So that the user knows something odd is happening, READLINE will prompt the user with a '?'. Whatever the user types in response will be made up into a list and 'returned' by readline as its 'result'. The '-> name' bit of the line says to store the result of readline in the variable 'name' for later use. If the user types, for example: i am not going to tell you my name then the statement: readline() -> name; will have the same effect as: [i am not going to tell you my name] -> name; The sixth line of the definition of converse makes use of this stored value: [pleased to meet you ^^name] => This tells POP-11 to print something with the VALUE of the variable 'name', not the word itself, inserted at the given point. The '^^' prefix tells POP-11 to insert the value of 'name'; if you omit the '^^' prefix then all POP-11 will print is: ** [pleased to meet you name] The seventh line of the definition is straightforward. The last line tells POP-11 that you have finished the definition. 'enddefine' is the 'closing bracket' corresponding to the opening bracket 'define', just as ']' is the closing bracket for ']'. -- HOW TO SWITCH BETWEEN TEACH AND VED --------------------------------- Look back at your file and check that you now understand what each line does. ENTER-ved (ie press ENTER, type 'ved' and then press RETURN) will get you to your file and ENTER-help (ie press ENTER, type 'help' and then press RETURN) should get you back here. Alternatively, you can switch between two files by pressing the ESC button (top left) and then X (for exchange). Try ESC X a few times. It may not work correctly when you have more than two files in VED. When you feel you understand how converse works, come back here and read on. -- WRITE YOUR OWN VERSION OF CONVERSE ---------------------------------- A valuable exercise would be to write a second procedure like converse but of your own invention. Here's a possibility: define doctor(); vars name, problem; [hello - what is your name] => readline() -> name; [tell me ^^name - what is it that worries you most] => readline() -> problem; [strange you should say that ^^name] => [i worry about ^^problem too] => [i see it is time for my next patient] => [goodbye ^^name] => enddefine; You can put this definition into the same file as your definition of converse. If you put two definitions of the same word in the same file then POP-11 will always use the one it has most recently read (ie the second one). -- MAKING CONVERSE CONDITIONAL ----------------------------------------- So far, the definitions considered do not use any 'conditional' statements. These are most important since they give a program flexibility to modify its behaviour depending on circumstances (provided all the options have been considered in advance by the programmer). Let's look at a simple conditional definition: define greet(); vars answer; [are you happy] => readline() -> answer; if answer = [yes] then [good] => else [oh dear] => endif; enddefine; Try out this definition. -- DOCTOR, A MORE COMPLICATED EXAMPLE ---------------------------------- Here is a more complicated example (read the following notes before trying it): define doctor(); vars answer; [are you feeling well] => readline() -> answer; if answer = [yes] then [you do not need my services] => else [do you hurt somewhere] => readline() -> answer; if answer = [yes] then [take two aspirins every four hours] => else [you need to see a specialist] => [make an appointment with the receptionist] => endif endif; [that will be $50 please] => enddefine; It will simplify copying these examples into your own file if, before giving the 'ENTER ved' command you position the text you want to remain visible at the bottom of the screen. Part of this text will still be visible whilst you are editing your own file. You can flit back to this file half way through by giving an 'ENTER help' command and then repositioning the help file text before returning to your own file. Also, you can try using the ESC-X command (ie press ESC then press 'x') to flit back and forth between two files. Alternatively, you could write it on paper but would that would rather be against the spirit of this course. -- DOCTOR EXPLAINED A LINE AT A TIME ----------------------------------- Let's examine the 'doctor' example in some detail - there are some interesting points about it. The procedure has four steps: (1) Printing [are you feeling well] (2) Reading a reply (3) A big conditional statement (4) Printing [that will be $50 please] Each step is 'separated' from the next by a 'separator'. The 'print arrow' (ie '=>') is a separator and so is the semi-colon. Step (3) has three parts: (3.1) A condition, answer = [yes] (3.2) What to do if the condition is true (3.3) What to do if the condition is false Step (3.2) is a simple print statement. Step (3.3), what to do if the condition is false, itself contains several steps: (3.3.1) Print [do you hurt somewhere] (3.3.2) Read a reply from the user (3.3.3) Another conditional statement The steps of (3.3) are separated from one another, either by '=>' or by ';'. Step (3.3.3) is itself a conditional statement and so has three components: (3.3.3.1) A condition, answer = [yes] (3.3.3.2) What to do if the condition is true, ie print [take two aspirins ...] (3.3.3.3) What do to if the condition is false Step (3.3.3.3) is itself a componunt step with two sub-steps: (3.3.3.3.1) Print [you need ...] (3.3.3.3.2) Print [make an appointement ...] Notice that step (4) (asking for the money) always gets 'obeyed' whatever happens in the two conditional statements. -- SUMMARY ------------------------------------------------------------- To summarize: procedure definitions can have several steps which are obeyed in sequence. These steps may be conditionals, in which case the condition itself will always be 'evaluated' and then one of the two 'arms' of the conditional statement will be evaluated. The arms of a conditional statement may contain several steps (including conditionals) and these sub-steps are obeyed in sequence. Steps of a procedure (or an arm of a conditional) should be separated either by a print arrow, ie '=>', or by a semicolon, ie ';'. -- A DIAGRAMMATIC EXPLANATION OF DOCTOR -------------------------------- We can illustrate what is happening diagramatically thus: [are you feeling well] => readline() -> answer; answer = [yes] ? if so ->->->->->: [you do not need my services] => | | | if not | | | [do you hurt somewhere] => | readline() -> answer; | answer = [yes] ? if so, ->->: [take two aspirins ...] => | | | | | if not | | | | | [you need to see ...] => | | [make an appointment ...] => | | | | | *-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<* | | | *-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<* | [that will be $50 please] => -- BIG PROCEDURES CAN BE BROKEN UP INTO SMALLER COMPONENTS ------------- It is often sensible to break up a procedure into several bits if it gets too complicated for you to take in at a glance. This isn't really necessary with the 'doctor' procedure of above since it is less than a screenful of text but if we did want to split it up here would be a good way: define doctor(); vars answer; [are you feeling well] => readline() -> answer; if answer = [yes] then [you do not need my services] => else feelbad() endif; [that will be $50 please] => enddefine; define feelbad(); vars answer; [do you hurt somewhere] => readline() -> answer; if answer = [yes] then [take two aspirins every four hours] => else [you need to see a specialist] => [make an appointment with the receptionist] => endif enddefine; Although the program is longer overall (since it now has two procedures in it rather than one) each procedure is simpler to understand than the original. -- AN EXERCISE: EXTEND THE DOCTOR PROGRAM ------------------------------ Try extending the 'doctor' procedure. Note that conditions need not be as simple as 'answer = [yes]'; you could have, for example: if answer = [head] then or if answer = [my back hurts most] then -- GUESS, A DIFFERENT SORT OF EXAMPLE ---------------------------------- As a final example of procedures created according to this basic pattern, we look at a simple program to play 'twenty questions' (though not very well): define guess(); vars answer; [think of a thing] => [is it animal vegetable or mineral] => readline() -> answer; if answer = [animal] then [does it have four legs] => readline() -> answer; if answer = [yes] then [i think it is a horse] => else [i think it is a person] => endif elseif answer = [vegetable] then [i think it is a carrot] => else [i give up] => endif enddefine; This example, introduces one new concept - the 'multi-way conditional' which has the form: IF first condition THEN actions to be done if first condition is true ELSEIF second condition THEN actions to be done if first condition is false and second condition is true ELSE actions to be done if neither condition is true ENDIF The word 'elseif' mustn't have a space in the middle of it. You can have as many 'ELSEIF condition THEN actions' as you like. Whilst you are permitted to omit the 'ELSE actions' bit, it is very unwise to do so. -- MAKE UP YOUR OWN EXAMPLE -------------------------------------------- Use you imagination to create some new procedure. It might be one to simulate a conversation with a car mechanic, trying to fix your car or it might be a conversation with a very rude person who just insults you the whole time or it might be a conversation with a ... -- BREAK BIG PROCEDURES UP INTO BITS ----------------------------------- Remember - as a general rule any procedure definition that won't fit on a the VDU screen is probably too big and should be split up into several procedures. If the 'guess' procedure gets too big it could be split up as follows: define guess(); vars answer; [think of an object] => [is it animal vegetable or mineral] => readline() -> answer; if answer = [animal] then guessanimal() elseif answer = [vegetable] then guessvegetable() else guessmineral() endif enddefine; define guessanimal(); vars answer; [does it have four legs] => readline() -> answer; if answer = [yes] then guessanimalfourlegs() else guessanimalnotfourlegs() endif enddefine; The four remaining undefined procedures (ie guessvegetable, guessmineral, guessanimalfourlegs and guessanimalnotfourlegs) would all have to be defined for the program to work. Notice that although file names can only be eight letters long, POP-11 procedure names can be as long as you like. Notice also how splitting up a big procedure in this way makes it much easier to understand since there are fewer 'nested conditionals' (ie conditional statements inside a conditional statement). Experience has shown that nested conditionals can make it harder to follow what is going on. -- INTRODUCING MATCHES ------------------------------------------------- We have already seen how a 'condition' can be used to see if two lists are identical, for example: if answer = [mind your own business] then ... This is often too rigid. For example, when the 'doctor' program asks: [where does it hurt] => the user might respond in any of the following ways: well it is my head that hurts most my head all over the back of my head It would be impossible to provide explicitly for all these cases. Fortunately, POP-11 provides a 'matches' procedure that is useful here. Here is a typical call of 'matches': if answer matches [??x head ??y] then ... This condition is true if the value of 'answer' contains 'head' anywhere; if answer doesn't contain 'head' then the condition is false. Moreover, the variables 'x' and 'y' will be 'set' to the words on either side of the word 'head'. To make this clearer, try the following example: [my head hurts] matches [??x head ??y] => x => y => -- A COMMENT ON CONDITIONS --------------------------------------------- If POP-11 complains about 'undeclared variables' then just ignore it; the POP-11 system is sometimes a bit pedantic (later you'll be glad of that). Notice how we can ask POP-11 to evaluate a condition directly, without having to write something as cumbersome as: if [my head hurts] matches [??x head ??y] then true => else false => endif; -- SOME EXERCISES USING MATCHES ---------------------------------------- Try the following examples, and look at the values of X and Y each time: [my mother makes apple pies] matches [??x mother ??y] => [my father makes apple pies] matches [??x mother ??y] => [father makes apple pies for mother] -> data; data matches [??x mother ??y] => data matches [??x father ??y] => data matches [??x apple ??y] => data matches [??x father ??y mother ??z] => data matches [??x mother ??y father ??z] => -- USING MATCHES TO EXTEND DOCTOR -------------------------------------- We can use 'matches' to write a far cleverer version of the 'doctor' program. In fact, this is precisely how the ELIZA program is written, so we call this program MYELIZA. I use this example to introduce several new ideas so you'll need to read the notes below the example to fully understand it. define myeliza(); vars x, y, z; [good day - what is your problem] => readline() -> z; until z matches [??x bye ??y] do respondto(z); readline() -> z; enduntil; [good bye] => enddefine; define respondto(list); vars x, y; if list matches [??x mother ??y] then [tell me more about your family] => elseif list matches [i want to ??x] then [do you know anyone else who wants to ^^x] => elseif list matches [i ??x you] then [perhaps in your fantasy we ^^x each other] => elseif list matches [??x ill ??y] then [have you tried the health centre] => else [how interesting - do go on] => endif; enddefine; This example introduces one new syntactic construction-the 'until loop'. The form of such a loop is: UNTIL condition DO actions ENDUNTIL When POP-11 comes across such a statement, it evaluates the condition and if it true does no more. If the condition is false then POP-11 performs the actions and then returns to retest the condition. This continues until the condition comes out true. Notice that it is not quite like an UNTIL in English since you might expect that POP-11 would break off in the middle of the actions if the condition became true half way through performing them. In this case, the condition is a simple call of MATCHES. The actions involve a call of a user defined procedure called 'respondto'. From the way RESPONDTO is invoked we can see that it needs an 'argument' (in this case the argument is the value of the variable 'z') and it prints something. -- RESPONDTO EXPLAINED -------------------------------------------------- The definition of 'respondto' also introduces some new ideas. Firstly, we see that 'respondto' is defined to need an argument which is referred to as 'list' in the definition. The name of the actual argument (which in this case is 'z') and the name of the 'formal argument' (which in this case is 'list') can be the same if you wish - it makes no difference. A fundamental principle of POP-11 programming is that the names used for variables in a procedure should not have any effect on the rest of a program. Notice that it is necessary to have a 'vars x, y;' statement in 'respondto' as well as in 'myeliza'; this is because the X and Y in 'myeliza' are 'local' to that definition and so 'respondto' must have its own X and Y, declared as local within it, using VARS. (For more on local variables try TEACH DEFINE and TEACH VARS. ----------------------------------------------------------- It would not affect the meaning of the definition of 'respondto' if all the variables in it were systematically renamed, for example: define respondto(arthur); vars mary, jane; if arthur matches [??mary mother ??jane] then [tell me more about your family] => .... enddefine; These variable names are a bit silly - but since their meaning to POP-11 is determined completely by how they are used you will only affect human readers of your program by using daft variables names. ------------------------------------------------------------- Make efforts to extend the definition of 'respondto' to get a more interesting conversation from the system. If you have time to spare then look at TEACH ELIZA2 which describes more complicated improvements that can be made to ELIZA. -- FURTHER READING ----------------------------------------------------- TEACH DATABASE is a good file to read next. --- C.all/teach/eliza -------------------------------------------------- --- Copyright University of Sussex 1992. All rights reserved. ---------- --- $poplocal/local/teach/eliza --- Copyright University of Birmingham 1999. All rights reserved. ------