TEACH RESPOND Revised A.Sloman, 27 Sep 1997 Updated Jun 2009 [Updated 12 Oct 1996 to use pattern prefix "!"] [This was written for first year University students. It can be simplified for younger students.] Please report any problems in this document to Aaron Sloman (Email a.sloman@cs.bham.ac.uk). The table of contents is near the end. -- Introduction ------------------------------------------------------- With the help of this TEACH file you will build a simplified ELIZA program, that can hold a 'conversation' in English. The original Eliza was a half-serious exercise in natural language processing developed by Joseph Weizenbaum at MIT in the USA. It loosely simulated a non-directive Rogerian psychotherapist. Many text books on AI include an account of Eliza, and a criticism of the techniques used, because they are so limited. Here is a sample of the Pop-11 Eliza at work. The upper case text was printed out by Eliza. ======================================================================= GOOD DAY WHAT IS YOUR PROBLEM? ? Do you remember our previous meeting? I NEED YOU TO TALK ABOUT YOURSELF BEFORE I ANSWER QUESTIONS ? I have missed you PERHAPS IN YOUR FANTASY WE HAVE MISSED EACH OTHER? ? Its very very real TELL ME MORE ABOUT YOURSELF ? My operating system is thrashing WHAT IF YOUR OPERATING SYSTEM WERE NOT REALLY THRASHING ? Then I could cope with the load SUPPOSE YOU COULDN'T COPE WITH THE LOAD ? I already cannot cope with the load DO GO ON ? You are adding to my load I AM ADDING TO YOUR LOAD ? you are adding to my load I WONDER IF YOU ARE AN INTELLIGENT COMPUTER? ? My brain sometimes wonders about that IS IT REALLY YOURS? ? Whose could it be HOW CAN WHOSE IT BE ? ? Your syntax is in a twist IS THAT TRUE OF ANY OF YOUR FRIENDS? ? Maybe my students REALLY? ? really EARLIER YOU SAID DO I REMEMBER OUR PREVIOUS MEETING ? ? we are going in circles PERHAPS YOU IS NOT REALLY GOING IN CIRCLES ======================================================================= There is a version of the Pop-11 Eliza available on the internet here: http://www.cs.bham.ac.uk/research/projects/cogaff/eliza If you try playing with it see if you can work out some of the rules it uses. That is very hard to do because it has a large collection of rules and the order in which they are tried is randomly 'shuffled' before each new sentence is processed. ======================================================================= By building your own mini Eliza you can o Learn more about Pop-11, and general programming techniques, including loops, conditionals, lists, variables, the pattern matcher, and interactive conversational programs (this is the main justification for this exercise). o Get a good feel for why this sort of technique will be inadequate for more sophisticated natural language processing, though useful for simple conversational interfaces. o Have practice at designing and documenting a program. o Begin to understand the importance of the architecture of a program. o Have a bit of fun. o Continue to develop fluency with the editor, etc. -- Running the Pop-11 Eliza Program ----------------------------------- The Pop-11 library has its own version of Eliza and you can try playing with it for a while to get a feel for what it does. Depending on how things have been set up you may be able to invoke Eliza from one of the mouse selectable menus set up for you. Otherwise try typing this to a the prompt in an xterm/console window. pop11 That will print out something like this (probably with a different version and date): Sussex Poplog (Version 15.62 Wed May 20 22:11:54 BST 2009) Copyright (c) 1982-2008 University of Sussex. All rights reserved. Setpop : The colon indicates that Pop-11 is waiting for you to type something. Type this on the same line as the colon, to invoke the 'eliza' program: eliza(); (including the semi-colon at the end) followed by the 'RETURN' key. If you forget the semi-colon, type it on the next line, followed by the RETURN key. Then follow instructions. -- When you have eliza running ---------------------------------------- It will start up with an introduction, beginning something like this: ELIZA HERE! This program simulates a non-directive psychotherapist. It will appear to engage you in conversation about your problems. followed by further information and instructions. You can type one sentence at a time to Eliza. End each sentence by pressing the RETURN key. You will then get a response from eliza. Don't go on too long: you can easily spend a lot of time having fun without learning much! When ready to finish, type a line containing only the word bye and press RETURN. If you then find you get a colon prompt, type "bye" again, to finally exit from Pop-11. -- Things to try on Eliza --------------------------------------------- There is no restriction on what you can type in: anything will produce a response. You will generally get more interesting responses if you make assertions rather than asking questions. Also (unless you are trying to find Eliza's limitations), avoid sentences with complex syntactic structures (e.g. subordinate clauses), and don't type more than one sentence on a line. If you are unlucky the results will be boring. You can try typing in some sentences that start with "I" and end with "you", such as I am better looking than you and you MAY get an interesting response. The results are not predictable because Eliza uses a randomizer to order its rules, and the same sentence typed in repeatedly can trigger different rules each time. Investigate how Eliza gets confused if you type in complex syntactic forms that include embedded sentences, to see how Eliza gets confused, e.g. I am very clever and so are you If you are conscious then you must be lonely You can also type complete gibberish and see how eliza responds, e.g. I fhlunk the rrudgnenenee 8-93333&*^*^* sdennn you Remember to end each of your assertions or questions by pressing the RETURN key. When finished, type "bye" alone on a line. You can now read on and learn how to build your own mini Eliza. -- Prerequisites ------------------------------------------------------ Before proceeding with this teach file you should be fluent in the use of the editor, and be able to create your own files, and mark and load lines of program. If you need revision you can go back to these teach files: TEACH MARK (How to mark ranges in VED files) TEACH LMR (How to Load a Marked Range in a VED file) TEACH VEDPOP (Introduction to programming using LMR in VED) TEACH BUFFERS (How to switch between files in VED) The last file introduces the syntax for defining simple procedures. More practice is given in TEACH RIVER. More elementary revision teach files on the editor are TEACH VED, WINDOW, SWITCHWINDOW, USEFULKEYS A summary of VED facilities is in TEACH VEDNOTES. You may scan that for reminders on how to do things. There is another teach file that introduces some of the key ideas of the Pop-11 pattern matcher, namely TEACH MATCHES. You can try working through that one either before this one or after it. -- The table of contents ---------------------------------------------- There is a table of contents below. You can move to it using the command ENTER g Then you can get to any section you desire by putting the VED cursor on the required line, and then again doing ENTER g Redo ENTER g at any time to get back to the table of contents. -- More specific overview of objectives for this file ----------------- In working through this TEACH file you will learn and practise a number of skills: 1) You will define procedures which take an input (often called an 'argument' and produce an output (referred to as a 'result'). Each procedure will have a name, as well as names for its input variables and its output variable(s). Some procedures also have other local (private) variables used for temporary storage. 2) You will learn about some of the structures in Pop-11, to which procedures can be applied, notably words, strings, and lists. 3) You will find out how to get POP-11 to obey a procedure with some input that you specify, and get the output printed out, using the print arrow. (This is an important part of testing your programs.) 4) You will link procedures together by making one procedure `invoke' another, giving it a structure as input to operate on. The second procedure may then produce some result or output, which is used by the first. The second procedure may do this partly by invoking yet more procedures, and so on. 5) You will make procedures which behave differently according to their input, by using 'if ... then ... elseif then ... else ... endif' expressions. I.e. you will learn about conditional instructions. 6) You will make POP-11 construct lists of words, using the brackets [ and ], and using the symbol "^^" to insert a list of items into another list. 7) You will use the 'matches' operation to distinguish different forms of lists, in order to decide which action to perform, and you will learn how to build patterns for the matcher to use, with "??" to specify variables that have to be assigned to. 8) You will build a larger procedure out of several smaller procedures, each of which performs part of the task of the big one. 9) You will learn to use the Pop-11 library procedure called readline, to get a line of input from the user, in the form of a list of words. 10) You will learn to use the editor VED to switch between three files: o your program file 'respond.p' in which you define your program, o the output file 'output.p' in which test commands are given and output appears, and o this TEACH file. The relevant features of Pop-11 are summarised in the TEACH POPCORE file (available in printed form), and in text books on Pop-11, as well as TEACH PRIMER, (also available in printed form). We'll start by introducing a subset of the techniques required for Eliza, and then design and implement a more elaborate version, later on. -- Planning the architecture of your program -------------------------- A program should have an architecture, like a house, or a machine. That is, there should be some well defined main components performing different tasks, like the different rooms of a house and also its walls, doors, windows, etc., or the different parts of a car (seats, driving controls, fuel system, electrical system, engine, lights, etc.) For a program like Eliza it may suffice to have two main components 1. An interface This deals with starting up the interaction with the user, finishing off the interaction with the user, and in between repeatedly getting input from the user and printing out a response. 2. An "inference engine" This is used by the interface. Every time the user types in a new sentence the sentence is passed to the inference engine to work out what the response should be. The response is handed back to the interface which then prints it out. Thus, we can define the interface as a sub-program which prints out a greeting, then repeatedly reads in a sentence and prints out a sentence, until the user types "bye". Then it prints out a farewell message and stops. We can represent the architecture diagrammatically: *------* --- > --- *---------* --- > --- *----------------* | USER | |INTERFACE| |INFERENCE ENGINE| *------* --- < --- *---------* --- < --- *----------------* You will define an interface procedure called "converse", and an inference engine called "respond". The interface procedure will need some subsidiary procedures getname (to ask for the user's name and read it int) greet (to print out a greeting) getproblem (to get a sentence typed by the user) farewell (to give a polite "closing" message when finished) The getproblem procedure will be used repeatedly in a "loop" instruction, and the sentence that is read in will repeatedly be given to the "inference engine" called respond. respond (take in a user's sentence and produce a reply) Defining these various procedures involves learning some pop-11 syntax, including the syntax for procedure definitions, for invoking a procedure with some input, for using the result of a procedure, for expressing "repeat" instructions (loops) and conditional instructions of the form: if ... then ... elseif ... then ... ... ... else .... endif We'll start with the greet procedure, then go on to more complex things. -- A first program to print out a response ---------------------------- A procedure definition follows, which you should put into a file called respond.p. If you don't know how to do that see TEACH VEDPOP. You will use your file respond.p to store various definitions of procedures. Initially they will be very simple, but as you read on you will learn how to make them more interesting. You will do this by repeatedly going back to them and editing them to extend what they can do. You can then test individual steps in the file and get the printout in a different file called output.p First define a procedure which takes no input, but produces a list as its result. define greet() -> result; lvars result; [Hello - I am here to help you] -> result; enddefine; Put that into your file called 'respond.p'. Later you will make it more complicated by editing it. In your own file, the word "define" should occur at the extreme left of the line, not indented as above. The first line says you are defining a procedure whose name is "greet" and which takes No input items when it runs, because there is nothing between the parentheses "()". The "-> result" bit says that the procedure has one "output local" variable whose name is "result", and which will be given a value whenever the procedure is run. The second line says that the output variable result is to be declared as a "local" variable for the procedure greet. Strictly the "lvars" means that it will be a "lexical local" variable. Later you will meet non-lexical variables, declared using "vars". In fact, because "result" is a variable in the header you don't need "lvars" (Since Poplog V15), and it will be omitted below. The semicolons at the end of each line are part of the POP-11 language and are essential (exceptions will be explained later). If you leave out semicolons, you may get syntactic error messages. Notice the square brackets, which used to make a list of words. (Pop-11 can also handle lists of numbers, strings, and other objects, including lists of lists.) The arrow "->" in the second last line is used to assume the list of words to the variable "result". This use of the arrow is different from the use in the procedure heading, though they are related. In the heading "->" means that something needs to be assigned to the variable so that its value can be used as a result of the procedure. Inside the body of the procedure, "->" can be used to actually do the assignment. These ideas will gradually become clearer and more familiar. Note on indentation: Many programming examples in TEACH files, like the above definition, are *indented* to make them stand out from the main text. You are advised to ensure that procedure definitions in your own files start with "define" aligned to the left, not indented. I.e. if an example in a teach file, like the definition of "greet" above is not flush against the left margin, you should make it flush left in your file. I.e. if it is like this in a teach file define greet() -> result; [Hello - I am here to help you]-> result; enddefine; it should be like this in your own file (in this case your file respond.p). define greet() -> result; [Hello - I am here to help you]-> result; enddefine; That will make it much quicker to compile a procedure. Instead of marking and loading it you can put the VED cursor in the middle of the procedure and type ENTER lcp (i.e. Load Current Procedure) which can be abbreviated to ESC c. You can then also use ENTER jcp (justify current procedure) to ask VED to format the procedure definition for clearer reading. It inserts indentation to show the structure of the procedure. -- Load and test the procedure ----------------------------------------- Once you have typed in the definition of the procedure into your file called respond.p, you need to load it, i.e. to compile the definition. You can mark and load the procedure using the methods described in earlier teach files, or you can combine "mark procedure" and "load procedure" with the key sequence: ESC c. That will work provided that the Ved cursor is somewhere in the procedure definition. In order to check that it does what is intended, you should ask POP-11 to run the procedure as follows. Put the following code (without indentation) into your file respond.p shortly after the 'enddefine' line: /* ;;; testing procedure greet greet() => */ The first and last lines use the comment brackets "/*" and "*/" to say that everything in between will be ignored if you compile the whole file. The second line is an "end of line" comment, which does nothing even if not between comment brackets. Pop-11 always ignores anything from ";;;" to the end of line. The third line is a test instruction which you can ask Pop-11 to obey even though it is inside a comment which will be ignored when the whole file is compiled. Get Pop-11 to load that line (the line testing greet) by using the LOADLINE command. Put the VED cursor on the line and press the loadline key (usually ESC D will do). (Or mark the line and do CTRL-D, for LMR). When Pop-11 obeys that command, it runs the procedure called "greet" (because of the "doit" brackets "()"). The procedure creates a list as its result, which is left on the Pop-11 stack. Then the print arrow "=>" prints out the result, thus: ** [Hello - I am here to help you] Test greet a few times till you are bored with the conversation! If you get both your 'respond.p' and 'output.p' files onto the screen at once so that this teach file becomes invisible, and you are not using Xved you can restore it with the command ENTER teach (Tips: You can use the "ENTER rb" (rotate buffer) command repeatedly in VED to get back to a previous file. Alternatively you can use "ESC e" to get a menu of files currently in VED and select one by typing its number.) Go back to your file respond.p, and edit your definition of the procedure to to make it create a different list of words. Then compile the procedure (ESC c) (or mark and load). Then redo the "greet() =>" command (put the Ved cursor on it, and use loadline (ESC d)) to see how the output changes. -- Reminder: Summary of load or compile commands in VED -------------- CTRL-d shorthand for ENTER lmr Load Marked Range ESC d loadline Load Current Line ESC c shorthand for ENTER lcp Load Current Procedure (Works only if procedure heading is left-aligned) ENTER jcp justify(format) current procedure. (Works only if procedure heading is left-aligned) Memorise those if you have not already done so. You'll also know how to mark the Start of a range (Key F1 (or ESC m)), and the end of a range (Key F2 (or ESC M)). F1 and F2 may not work on all terminals. -- Making GREET's behaviour more varied ---------------------------- You can make the greet procedure a little more flexible, by getting it to print out something which depends on its input. Suppose we want it to be told somebody's name each time it runs, and to print out a greeting for that person. For example, we might like the command greet([sally squirrel]) => to print out the following: ** [Hello sally squirrel - I am here to help you] And similarly greet([freddy fox]) => ** [Hello freddy fox - I am here to help you] In order to do this you need to change the definition of the procedure greet so that it has an input variable, to correspond to different possible inputs ([sally squirrel], or [freddy fox] for example). The list that greet creates should include the elements of the input list. For this we use the "double up arrow" symbol "^^" (sometimes described as "double hat") which means, roughly, "insert elements of list". So change your definition of the procedure greet in your file respond.p to read thus (but with "define" aligned left): define greet(name) -> result; [Hello ^^name - I am here to help you] -> result; enddefine; If you have trouble finding the "^" symbol on your keyboard, try looking above the "6". It is often there. Notice the introduction of the new input variable "name" on the first line. The output variable "result" is as before. Pop-11 will automatically declare both the input variable and the output variable as lvars, i.e. local variables in the procedure greet. Note that "name" here is a local variable, accessible only within the procedure greet. There may be other variables called "name" used elsewhere in your program, but they will not interfere with this one because it is local. Notice how the following in a list ^^name is used to mean insert the elements of the list called "name" in a larger list The second line of greet creates a new list every time the procedure is run, using whatever value is associated with the variable name. Each time greet runs, the elements of the list called name are spliced into a bigger list which is "assigned to" the output local variable called result. Thus if different inputs are given, different lists will be created, which is what we required. -- Testing the new definition ------------------------------------------ Now try the compiling the new procedure using ENTER lmr, or ESC c. Change the test for greet after your revised definition to something like this: greet( [father christmas] ) => Mark and load this, or use ESC d, to run vedloadline. Try again with different things between the square brackets, and see how it affects what is printed out. You could try including your own name. In other words, try giving the procedure "greet" different arguments. ("argument" is just another name for the "input" to a procedure.) -- GREET explained a line at a time --------------------------------- Let us take the definition of greet a line at a time and work out what it is doing. The first line: define greet(name) -> result; This is the procedure "heading". It tells the POP-11 system that you want to define a new procedure whose name is to be "greet". The fact that there is ONE symbol between the brackets says that the procedure takes ONE item as its input, or 'argument', whenever it is run. Since we don't know what the actual input will be we use a 'variable' called 'name' to refer to it. Similarly " -> result" says that the procedure will produce one output, which is whatever the value of "result" happens to be when the procedure finishes. "result" is an output local variable. The second line of the definition is: [Hello ^^name - I am here to help you] -> result; This builds a list of words and assigns it to the output local variable, result. Notice that greet does not itself print out the list. It "returns" the list as its result, and whichever procedure invoked it can decide what to do with the result. The list contains: ^^name. This indicates that the 'value' of 'name' (i.e., the object it stands for) is inserted in the list at that point. Assuming that the value of 'name' is a list, then all the elements of the list called 'name' are included. So if 'name' stands for [father christmas], then doing: [Hello ^^name - I am here to help you]; will produce: [Hello father christmas - I am here to help you] The last line of the procedure 'enddefine;' tells POP-11 that you have finished the definition. It will automatically add an instruction to ensure that value of the output local variable, result, is left on the stack when the procedure finishes. When you've written the procedure, load it (MARK and LMR, or ESC c), then, in your 'output.p' file (ENTER ved output) test it with various arguments of the form: greet([ ...... ]) => I.e. type different things in place of the dots. When you have tested greet you can save it in your respond.p file, as it will be useful later on as part of the complete Eliza program. -- Testing GREET with different sorts of inputs ----------------------- Try running greet with different arguments, in your 'output.p' file. E.g. try your own name, or some phrase that is not a name: greet( [ I loathe you, but ] ) => It is important that you give greet a LIST as its input (or "argument"). That is why the square brackets are there. If you try to give it a number greet( 999 ) => or a word greet( "joe" ) => or an undeclared variable greet( joe ) => then you will get a mishap message. Try those in your 'output.p' file to see what the mishap messages look like. (If you learn how to read mishap messages, it will help you cope later with unintended mishaps.) A list of numbers will not produce a mishap, though what is printed out may look like nonsense. Try greet([1 2 3]) => -- Local variables can have their names changed ----------------------- Notice that Pop-11 does not understand the word 'name', or the word 'result' used in the definition of greet. Both are just used as arbitrary labels for locations in the computer memory where your program can store information. They are "local variables" used by the procedure greet. Thus you could replace both occurrences of "name" with "nom" or with "xxxx" and the procedure would still work. You could test that. However, it is advisable to use "meaningful" names for variables to help you remember what they are for when you come back to work on the program later on. So don't use short names like "n", or "x", and don't use gibberish names like "xxxx". -- Write your own version of GREET ----------------------------------- Look back at your definition of greet. Imagine it is going to be used by an Eliza-like program to start off a conversation with a user whose name has just been typed in. Modify the instructions in greet so that when you test it it prints out a suitable message prior to the conversation. I it could include the user's name in more than one sentence. E.g. the printout when greet is given the name [fred bloggs] might be something like this. ** [hello fred bloggs I am here to help you] ** [you can ask me questions or tell me facts about yourself] ** [I shall really do my best ] ** [I am looking forward to our conversation, fred bloggs] Modify your program to suit yourself and then test it, with your name. -- Define respond ----------------------------------------------------- Now let's consider how to define a procedure called "respond" which will work out what to say in response when a user types in a problem. This procedure is the "inference engine" in the architecture described above, though in this case it is a fairly simple inference engine. For now let us assume that we already have the user's problem statement in the form of a list of words, just as we previously assumed we had the user's name in the form of a list of words. See if you can define a procedure which is called "respond", and which, like greet, is given a list as input and produces a list as output. But The output list should be something suitable for a sympathetic therapist or counsellor to print out. E.g. if I give it the list [I wish I could fly] then perhaps it should create a new list saying [I am sorry to hear you say I wish I could fly] The format of the program, which you should type into your file respond.p, after the definition of procedure greet, could be something like the following. Notice that the second line is a comment, saying what the procedure does. Wherever possible you should insert comments in your programs, explaining what procedures are for. Wherever Pop-11 finds three semicolons in a row ";;;" it treats the rest of the line as a comment, which it ignores. define respond(input) -> result; ;;; Create a response to the user's input list [ ... ^^input ...] -> result; enddefine; Change this to have suitable words in the fourth line in addition to "^^input". I.e. the list created on line 4 should include the elements of the input list, plus some other suitable words. Now test your procedure by means of commands like: respond([the weather is very poor today]) => respond([please tell me how to get rich]) => At this stage the responses produced will not be very interesting or very varied. Later we shall extend the procedure respond to do better than this. But even in this form we can use it as a building block in a larger procedure. When all the components of the larger procedure are working, you can come back to respond and extend it, using the larger procedure as a testbed. -- Work still to be done ---------------------------------------------- So far you have learnt how to create a procedure that takes a list as input and returns as a result another list that depends on the contents of the input. But this is only a small subset of the task of designing an eliza-like program. You might try writing down what tasks remain, and then compare your analysis with the suggestions in the next section. This is an example of the discipline required for software development: a. try to analyse requirements of a problem, then b. try to design a solution, then c. implement and d. test the solution, and possibly, in the process, modify the requirements and the design, in the light of what you learn. -- Outline of remaining tasks ----------------------------------------- There are several subtasks that remain to be achieved in order to produce a working version of Eliza. Compare the following with your own list of remaining tasks. -- -- 1. Generalise respond ------------------------------------------ If the procedure respond is to be given the input typed by the user, and must work out what should be said in reply, it will need a much more flexible structure. One such structure would be a sequence of conditional tests of the form: if the input is of type1 then create a list ..... elseif the input is of type2 then create another list ..... elseif .... then ...... and so on .... else create a default response endif We can fill in details later. The main point for now is that this is a "multi-branch conditional" instruction. This one shows only three tests, though in principle there could be more. A conditional can be as long as you think is needed to deal with all the different cases. We'll return to this later on, after listing the other main tasks to be done. -- -- 2. Design a controlling procedure (converse) ---------------- It is not very pleasant repeatedly to have to type: respond([ ..... ]) => with some words in place of the dots. So we should define a controlling procedure, which, after an introduction, repeatedly asks the user to type in a list of words, and then, each time, uses respond to get a suitable answer to what the user has typed. That is roughly what enables the Eliza program to have an ongoing conversation once it has been started. The controlling procedure could be called "converse" and might do the following: 1. Find out the user's name. We could define a procedure called "getname" to do this. The name can be stored in a global variable so that it can be used by some of the programs which work out responses to the user. 2. Print out some introductory message. You could use your procedure called "greet" (defined above) to do this. 3. Repeatedly do the following steps: a. ask the user to type in a comment or query We'll define a procedure called "getproblem" to do this b. read in and make a list of those words, We'll use the built in Pop-11 procedure called "readline" for this c. give the input list to the procedure respond, and print out the output from respond, using "=>" A minor complication is that any repetitive program has to know when to stop! We could agree that this repeat loop should stop if ever the user types simply "bye". So we can have a command of the form quitif( input = [bye] ); 4. Finally respond can print some sort of 'farewell' message. We could define a procedure called "farewell" to do this. Thus the format for converse will be something like this, using English instead of Pop-11. vars user_name; ;;; global variable available for all procedures define converse(); 1. declare local variable, input 2. use getname to get the user's name, and assign to user_name 3. use greet to give the user a greeting 4. repeat use getproblem to invoke readline to get a list of input check if user typed bye, and if so quit the loop use respond to get a reply to the user, and print it endrepeat; 5. print a farewell message enddefine; -- A framework for converse ------------------------------------------- In defining the procedure converse, it is useful to remember the architecture diagram given in an earlier section, showing the user, the interface and the inference engine. The interface and the instruction to invoke the inference engine will go into the procedure converse. The inference engine which works out what to say is the procedure respond. The Pop-11 version could look like this, though it uses procedures that we have not yet defined (namely getproblem, and farewell), so it cannot yet be tested. Notice that the procedure uses two "local" variables, called "name" and "input" that are declared using "lvars". They do not occur in the procedure header so they must be declared explicitly. vars user_name; define converse(); lvars input; getname() -> user_name; ;;; getname has not yet been defined greet(user_name) => repeat getproblem() -> input; ;;; get input from user quitif(input = [bye]); respond(input) => endrepeat; farewell(user_name) => ;;; print farewell message enddefine; You may be able to think of ways in which this high level design might be elaborated. But for now this will suffice. -- Getting the user's name: defining GETNAME -------------------------- The procedure getname will print out a message, and then read in a line typed by the user. The line of text can be read in by using the Pop-11 built in procedure called "readline". You can test readline thus: readline() => ask Pop-11 to obey that (using LOADLINE, or ESC d). VED will switch to the output.p file and prompt you with "?". You can then type some words, and press return. readline will make a list of those words, and leave the list on the Pop-11 stack. The print arrow "=>" will print out the list. Try it. We can use that in a procedure called "getname", thus. define getname() -> name; ;;; This prompts the user to type his/her name, returned as a list ;;; Print out a string as a message: 'Good day. Please type your name, and finish with RETURN' => ;;; read in the user's name as a list of words readline() -> name enddefine; Copy that (with a suitably modified message string, if you prefer) to your file 'respond.p'. -- Put test commands inside comment brackets -------------------------- After the procedure definition, you can add a test command, inside the comment brackets "/*" and "*/", so that the test will not be run when you load the whole file, thus: /* ;;; test getname getname() => */ Note that we have used two forms of Pop-11 comment: 1. The "end of line" comment which starts with ";;;" and only lasts till the end of the line. 2. Comments between "/* and */". These can go over as many lines as you like, as in the above example. You can put some test commands in such a comment and later use mark and load (or LOADLINE, or ESC d), to run that test. However if you later load the whole file the test command will not run because it is inside a comment. Test commands for individual procedures are needed only for development and testing, not for when the whole program is later loaded for use. You can test getname a few times. When it prints the message, followed by the readline() prompt "?", type in your full name, and press return. Your name will be printed in the form of a list of words, between square brackets. -- Combining getname and greet ---------------------------------------- Make sure your procedure called greet has been compiled (put the cursor in it and type ESC c, or mark and load it). You can combine getname and greet in a piece of program, as follows: vars user_name; getname() -> user_name; greet(user_name) => Test that by marking and loading all three lines. (Mark the lines, then ENTER lmr, or CTRL d) Look back at the outline definition of the procedure converse. You should see that the combination of getname and greet enables the first part of converse to work, i.e. the bit of converse before the beginning of the "loop" with these brackets repeat .... endrepeat But there are still some more procedures to be defined. -- Asking the user to state a problem or ask a question --------------- You now need to define a procedure, called "getproblem" which will be used repeatedly, inside the repeat loop, to ask the user to state a problem. It will print out a "message prompt" encouraging the user to type something, then use readline to read in the user's answer. The result of readline will be the result of getproblem. You should now know enough to define a procedure called getproblem with the following beginning and ending. Put the definition in the file respond.p Decide what to put in place of the two lines of dots. define getproblem() -> sentence; ..... ..... enddefine; 1. All you have to do to replace the first line of dots is add some Pop-11 to print out an instruction for the user. The instruction could be the list of words [Do go on] or the list [Please state your problem] or something else. Don't try to make your list longer than one line. Avoid using the apostrophe "'" as in "don't". End your command with the print arrow "=>" as usual. 2. To replace the second line of dots insert a call of readline(), and assign the result of readline to the local variable sentence. HINT: you can model your procedure getproblem on the procedure getname, defined above. When you have put a suitable definition of getproblem into your file respond.p you can test it thus: getproblem() => You could insert that test between comment brackets "/*" and "*/" in your file, as was suggested above for testing getname. Immediately after the procedure heading insert a comment explaining what the purpose of the procedure getproblem is. E.g. something like ;;; Print out some encouragement and read in user's sentence -- Defining farewell -------------------------------------------------- The final bit of your converse program will invoke the procedure called farewell, so you might as well define it now. Make it print a message suitable for terminating a conversation. It will be given a list containing the user's name. so it can use the double up-arrow "^^" to include the user's name in the farewell message. define farewell(name) -> result; [ .... ^^name ... ] -> result; enddefine; Notice that the procedure farewell is given the user's name as input. It does not need to know that previously that name was a list of words held in the global variable "user_name". Once it has the list, it uses its own local variable "name" to refer to the list. You could get farewell to try to charge a fee for consultation! After putting a suitable definition of farewell into your respond.p file, you can test it: farewell( [spice girls] ) => -- Testing the whole of converse -------------------------------------- You could now add the converse procedure defined above to the end of your respond.p file. vars user_name; define converse(); ;;; YOU SHOULD PUT A COMMENT HERE, SAYING BRIEFLY ;;; WHAT CONVERSE IS INTENDED TO DO lvars input; ;;; Print a message and get user's name. Assign to global variable getname() -> user_name; ;;; Greet the user greet(user_name) => ;;; Now repeatedly get input from the user, and print a ;;; response to it. repeat getproblem() -> input; quitif(input = [bye]); respond(input) => endrepeat; farewell(user_name) => enddefine; You can then test this program by typing converse(); The conversation will not yet be very interesting, mainly because the procedure respond has not yet been made flexible enough. But we can return to respond and remedy that, as suggested below. Note that that test command should not be in the same file as the procedure definitions, unless you put comment brackets "/*" and "*/" above and below the test. -- The control structure used in converse ----------------------------- It is worth noting that the format of converse is useful for many interactive programs. We used the following structure to tell Pop-11 to do the same action repeatedly: repeat endrepeat This will repeat the forever. But we want to make it stop when the user types a special word ("bye"). This was done using quitif in the following format: repeat ; quitif( ); ; endrepeat; This means 1. do action 1 2. test condition - if it is true then jump after endrepeat 3. do action 2 4. go back to 1 Thus action 1 could ask you to type your problem. The condition for quitting can be that you have typed: bye, then action 2 can deal with all the other possible queries. In the procedure converse, as defined above, we made the stopping condition depend on the test input = [bye] If this test has the result true, then the remaining actions in the loop will not be done, and the loop will terminate. If you wished the user to type something else to indicate termination you could change that instruction. Alternatively you could allow the user to give more than one "terminating" comment, as follows. quitif(input = [bye] or input = [enough] or input = [quit]); Note that "or" can be used to join several conditions together. Pop-11 will then test the first one, and if that comes out false will test the next one and if that comes out false will test the next one. If any of them comes out true the whole condition will be treated as true. If they are all false, the whole condition is treated as false, and in this case the following instruction is obeyed, namely respond(input) => -- Making RESPOND more interesting ------------------------------------ Remember that respond takes in the list of words typed in by the user and produces a list of words to be used as the reply that will be printed out by converse. We want to cover a variety of possible inputs, and match them with suitable responses. We'll assume for now that the user will type in either [i hate computers] or [are you intelligent?] and in the first case the reply is to be [perhaps you hate yourself] and in the second case [do i seem intelligent?] If the procedure is given a query that it can't answer then we want to produce a 'default' reply like [please go on] Note that we have used lower case letters throughout. POP-11 distinguishes between upper and lower case letters, so the lists [Father Christmas] and [father christmas] are not equivalent. This can cause problems when we come to recognising queries, so from now on all the words will be in lower case (a more sophisticated query answering program would recognise the two lists as equivalent). Also we will avoid apostrophes, such as the one in: shouldn't, since (unfortunately) POP-11 treats the apostrophe in a list as a special symbol. In order to distinguish between the two different queries we need to use a 'conditional' instruction. So go back to your respond.p file, search for the definition of respond, and change it to be something like this: define respond(input) -> result; ;;; Create a response to the user's input list if input = [i hate computers] then [perhaps you hate yourself] -> result; elseif input = [are you intelligent?] then [do i seem intelligent?] -> result; else [please go on] -> result; endif; enddefine; The body of the procedure is a multi-branch conditional instruction: if input = [i hate computers] then [perhaps you hate yourself] -> result; elseif input = [are you intelligent?] then [do i seem intelligent?] -> result; else [please go on] -> result; endif; In this case there are only three branches. But we can add more. Note that this assignment instruction [perhaps you hate yourself] -> result; is carried out ONLY if the condition input = [i hate computers] is true. Otherwise, if the next condition is true input = [are you intelligent?] then the following assignment instruction is carried out, namely [do i seem intelligent?] -> result; If neither of the two conditions (preceding "then") are true, then the final default, or "else" instruction is obeyed, namely [please go on] -> result; The 'value' that result has at the end of the procedure returned as the result of the whole procedure respond. You can also include the value of the global variable "user_name" in one or more of the responses, e.g. [ ^^user_name please go on] -> result; -- Testing the 'respond' procedure ------------------------------------ When you have the revised version of respond in your 'respond.p file, load it and try it out in your output file. respond([i hate computers])=> respond([are you intelligent?])=> respond([can you help me?])=> Make sure that it produces the appropriate response each time. Then see what difference it makes to converse: converse(); Try typing in those sentences, without the list brackets, when you are prompted to type something in by converse. -- Extending respond ------------------------------------------------- You can add as many other 'elseif ... then ....' clauses as you want. These should be placed before the final 'else' condition. For example, if you want the procedure to reply to the question [can you help me?] then you might insert the following two lines before the "else" line: elseif input = [can you help me?] then [i will try to help] -> result; Try adding these lines, and some others of your own choice, to extend the conditional instruction in the procedure respond. Then test your program in the 'output file', as before, by running converse() Don't spend much time on this, as there are more interesting and general things to be done, described below. -- The structure of the conditional (if) instruction ------------------ You should make sure you understand what is happening between 'if' and 'endif'. This is called a 'multi-branch' conditional instruction with a default at the end. The form can be represented diagrammatically thus (assuming you've added the [can you help me?] query): [i hate computers] if so ->->->->->->--- [perhaps you hate yourself] | | | if not | V | [are you intelligent?] if so ->->->-- [do i seem intelligent?] V | | | | if not | | V | | [can you help me?] if so ->->->-[i will try to help] V V | | | | | if not | | | | | | | (else) | | | V | V | [please go on] (DEFAULT) V | V | | | | (endif) | | | | | | | *-----------<------------<-----------*--------<---*-----<---* | (end of procedure) In our example there are three branches to the right, but Pop-11 does not set an upper limit. Conditional instructions can have as many elseif...then clauses as required. Notice that all the different branches join up at the end, and the procedure comes to an end. The value of the output local variable "result" is then left on the stack. You have to make sure that by then it has a sensible value, otherwise a meaningless default result may be produced. -- Revision exercise -------------------------------------------------- Try writing down down, in ENGLISH, with diagrams, an explanation of what the following format means to POP-11? if then elseif then else endif; Compare your answer with the above. Check your explanation with that produced by other students. -- Making respond even more flexible ---------------------------------- There are two major problems with the procedure 'respond' as a basis for a conversation program. First, the form of input that can be answered is very rigidly determined. It will not give a useful answer to: [i hate computers that pretend to understand] or [can you tell me if you are intelligent] or even [can you help me] It will not recognise the last query because it does not end with a question mark. The other, rather more difficult, problem is of course that the program does not understand the meaning of the question, it merely manipulates symbols. We could add lines of total nonsense to 'respond', such as: elseif input = [pibble bobble plt] then [pssst wheeee] -> result; and it would respond to: respond([pibble bobble plt])=> with ** [pssst wheeee] We will avoid the question of how a program can represent the meanings of words, and instead consider how to make Eliza more flexible. Below is a reminder of the sort of thing we need to achieve. Later we'll consider how to achieve it. The more advanced teach files TEACH RIVER2 and TEACH RIVERCHAT explain some ways of giving a computer a primitive understanding of the meanings of simple commands and questions. -- How Eliza uses pattern matching ------------------------------------ As you will have seen from playing with ELIZA, the program does not 'understand' English, in the sense of building up a representation of the meaning of a sentence. Instead it relies on a range of 'tricks' to manipulate patterns of words. For instance, if you type in a very short statement, such as no then ELIZA responds with a phrase like: you are being somewhat short with me which is taken at random from a 'pool' of acceptable replies. So the first trick is to select an output randomly from a list of prepared sentences. Another trick is to check whether the user typed in a sentence using a particular sort of key word, e.g. "mother", or "money", and then respond with a previously prepared sentence, e.g. tell me more about your family or are you very rich In addition to recognising 'keywords', such as 'mother' or 'computer' contained in a sentence, Eliza has a more subtle trick which enables it to use some of the words typed in to build up its output sentence. It can match and respond to 'patterns' of words. For example, if you typed AI students can program computers ELIZA might respond suppose AI students could not program computers The program knows nothing about the programming abilities of students, it merely matches sentences of the form can and produces a response containing parts of the input, eg: suppose could not Thus, to the sentence: mrs smiths cat can program computers It might reply suppose mrs smiths cat could not program computers -- The role of the Pop-11 pattern matcher ----------------------------- This type of conversation is fairly simple to program in POP-11, since the language has built-in facilities for 'pattern matching'. The pattern can might be written in POP-11 as: [ ??thing can ??action ] and the response suppose could not might be written as [ suppose ^^thing could not ^^action ] Notice how we used the symbol "??thing" to mean roughly "Set the value of the variable thing to a list of items] and the symbol "^^thing" to mean roughly "Replace this item with the value of the variable thing, which should be a list of items." Thus if you were to match the list of words [fat pink pigs can fly far] with the pattern [ ??thing can ??action ] Then the variable thing would get the value [fat pink pigs] and the variable action would be given the value [fly far] Then the list expression [ suppose ^^thing could not ^^action ] would cause Pop-11 to create the list: [suppose fat pink pigs could not fly far] You can now learn how to write such patterns and incorporate them in a program. -- Using 'matches' --------------------------------------------------- It was suggested above that you extend the procedure respond to include this condition: elseif input = [can you help me?] then [i will try to help] -> result; This uses a very rigid test, because it uses the operator "=" to compare the input list with the list in the condition. For that equality test to produce the result true, the list must have exactly these five elements in this order: the words "can" "you" "help " "me" "?" Note that POP-11 splits "help" and "?" into two words even if they are not separated by a space. If the list has any other words, e.g. [can fred help me?] the condition will produce a false rather than a true result, and the command following "then" will not be carried out. The POP-11 matcher can be invoked by replacing the word "=" with the word "matches" which takes a list on its left and a pattern (which is a type of list) on the right, and produces a true or false result, depending on whether the list matches the pattern. The tests used by "matches" are more flexible than those used by "=". So you can construct a more flexible test, using a condition that accepts ANY input list containing the word "can" followed immediately by "you" as follows: elseif input matches [== can you ==] then [i will try to help] -> result; Note that each occurrence of the double equals symbol, i.e. "==", will match ZERO OR MORE elements in a list so [== can you ==] would match [please can you help] or [can you help me] or even just [can you]. Try changing the third condition of your respond procedure to elseif input matches [== can you ==] then Having made this change, you can now test the procedure. Type in various lists containing the two words "can" and "you". What will happen if you test the procedure with respond([can you play the tuba]) => This shows up a general limitation of 'pattern matching' methods of language understanding. You either construct a too rigid pattern to be matched (as with the original respond procedure), in which case it will not respond to valid queries, or one which is too flexible (as with [== can you ==] above) in which case it will match queries that should be ignored. One solution is to write a large number of (fairly) rigid patterns like [== can you help me ==] and develop a huge multi-branch conditional to deal with all of them. But this is tedious and may not catch all the possible forms of input. Another solution is to 'parse' the sentence to uncover its grammatical structure. This is a DIFFICULT problem, central to A.I. research on natural language processing. (If you are interested in finding out more about this then take a look at the (optional) file TEACH WHYSYNTAX.) For many applications, particularly ones where the user can be given a little training in types of query that are acceptable to a program, then pattern matching may be quite sufficient. Try altering some of the conditional tests in your respond procedure to match patterns, i.e. using "matches" rather than "=". The order of conditions is important because the input is matched against each pattern in turn. Thus if we have two conditions, of the form if input matches [== can you ==] then [possibly] -> result; elseif input matches [can you help me] then [i will try to help] -> result; then the second pattern will never be matched. This is because EVERY list containing the word "can" followed by "you" will match the first pattern, even the list [can you help me]. Thus, it is necessary to put the most rigid patterns first and more general 'catch all' patterns, like [== can you ==] later. Try altering the other conditions to use pattern matching, so as to make your procedure more general. -- Using some of the input to build the output: pattern variables ----- Suppose the input is [i want to turn green] and you'd like the response to be [ i would not advise you to try to turn green] You could add the following clause using a pattern variable "??x": elseif input matches [i want to ??x] then [i would not advise you to try to ^^x] -> result If the match succeeds, then the matcher will assign to the pattern variable x a list containing all the words following i want to in the 'input' list. Then the use of "^^x" to create an output list will cause those words to be inserted after "try to". E.g. if the input is the list [i want to become prime minister] the variable x would get the list [become prime minister] as value, and the list assigned to result would be [i would not advise you to try to become prime minister] -- Making a pattern variable work in a procedure: using "!" ----------- Because a pattern variable preceded by "??" or "?", e.g. "x", is used as a variable it needs to be declared, and it should be made local to the procedure so that it cannot interact with other procedures using x as a global variable (which is not recommended anyway). However if you declare a pattern variable x using "lvars x" there is a problem. The occurrence of "??x" in the pattern and the "^^x" in the list created for the result will not be treated as referring to the same thing. That is because the pattern is INTERPRETED by the matches procedure, and if it finds a word after "??" or "?" it cannot link that word to the lexical variables declared using lvars in the procedure. The reasons are technical and will not be explained here (experts can look at the file HELP * LVARS). There are two solutions to the problem. (a) You can declare the variable x as of type "vars" by inserting, near the top of the procedure vars x; and doing the same for all other pattern variables preceded by "??" or "?". Until Poplog Version 15.0 was released that was the only solution, and that is the solution you will find in many of the older teach files based on the matcher. (b) You can use the special pattern prefix "!" (available in Birmingham) before each pattern containing pattern variables. The above would then become: elseif input matches ! [i want to ??x] then [i would not advise you to try to ^^x] -> result The use of "!" before a pattern inside a procedure has two effects. First it ensures that all pattern variables are declared as lvars, if they have not already been declared in that procedure. Second, it changes the pattern so that instead of containing a word after "??" or "?" it contains the lvars identifier record corresponding to that word. This means that the matcher can change the value of the variable so that the can be used in other instructions in that procedure, like the occurrence of "^^x" in the instruction to build the result list. Try putting those two lines, including "!" into your procedure 'respond', and and test it with different lists as input, including some which start "i want to". If you are short of time, skip the next section. -- Further options ----------------------------------------------------- Here are some more variants you can put in. (A space has been left after "!" in these examples to make it more visible, though it is not strictly necessary): elseif input matches ! [i ??x you] then [perhaps in your fantasy we ^^x each other] -> result; elseif input matches ! [??x is ??y] then [what if ^^x were not ^^y ? ] -> result; If you use patterns prefixed by "!" then near the top of the procedure you must declare the pattern variables as lvars, i.e. lvars x, y; ;;; pattern variables. Try your own variants and test them out. By now you might have a version of respond looking something like the following in your file respond.p: define respond(input) -> result; ;;; Create a response to the user's input list lvars x, y; ;;; local variables if input matches [i hate == ] then [perhaps you hate yourself] -> result; elseif input matches ! [are you ??x ] then [do i seem ^^x] -> result; elseif input matches ! [i ??x you] then [perhaps in your fantasy we ^^x each other] -> result; elseif input matches ! [??x is ??y] then [what if ^^x were not ^^y ? ] -> result; else [please go on] -> result; endif; enddefine; You should add some more "elseif ... then ... " lines checking out that you understand how to use "??" to save part of a list in a variable and how to use "^^" to insert a list into a bigger list. After the definition of respond you should have some tests between "/* ... */" comment brackets, e.g. /* respond([can you play the tuba]) => respond([do you hate your mother?]) => respond([i can climb higher than you]) => ... etc. ... */ Make sure you include at least one test example for each pattern you have included, to ensure that it produces the expected sort of output. TEACH * MATCHES (Birmingham version) will tell you more about the pattern matcher, and the prefix "!". HELP * MATCHES, gives a summary for more experienced programmers. -- Comment on how readline works -------------------------------------- Readline doesn't take any argument, but it does produce a result, a list. It gets the list by waiting till you have typed something on the terminal, and pressed the RETURN button. When you have done that, it makes a list of all the words you typed, and that list is its result. If you use it inside VED, then it runs a special version of the procedure that allows you to do other things in VED (e.g. look at another file) before you finish typing in your response in the output.p file. This means that you can interleave actions in a way that is not always possible with dialogue systems. -- Coming back after logging out --------------------------------------- If you save your file (using ENTER bye or ENTER q) and then resume work on it at some later session you type 'ved' followed by the file name, eg: ved respond.p This will open the file, but you then need to load the procedure again, by marking and loading it. If you have created more than one procedure you will need to mark and load them all. You can load all the procedures in the current file with the command: ENTER l1 (That's lower case "L" followed by the number "1") It is important when you do this that your file is not littered with test commands as they will then be run unnecessarily. So you can either put them in another file, or "blank them out" with comment brackets in this format /* test command 1 test command 2 test command 3 ..... */ You should also now insert some comments at the top of your file stating what the file is about, and listing the main procedures, with a brief description of what each one does. You can use the command ENTER fileheader to prepare a comment at the top of the file. You should complete the blanks in the header, and correct anything that is wrong. When you have done that show the file to a demonstrator, and show the demonstrator how your program works, by giving a test run. You can insert a comment before each procedure definition, explaining what the procedure does. To prepare a comment put the Ved cursor in the procedure definition and do ENTER procheader That will start a comment describing the procedure. You should complete the blanks in the comment, and correct anything that is wrong. -- The structure of your file respond.p ------------------------------- Your file should now have the following structure: 1. Header, saying what the file is about, who created it and when. 2. A further comment saying briefly how the program works. E.g. summarise the architecture. 3. If your program uses any libraries, then you can include "uses" commands to ensure the libraries are loaded. In this case all the libraries are autoloaded. Otherwise you would need this at the beginning: uses readpattern ;;; ensure that "!" works with patterns. 4. A succession of procedure definitions, each preceded by a comment saying what it does, and followed by or preceded by some test commands also in comment brackets. 5. Some final test commands to run the whole program. In this case a single command suffices: /* converse(); */ If you use some procedures before they are defined, then at the top of the file you can declare their names, to prevent "DECLARING VARIABLE" warning messages, e.g. with something like this vars respond; ;;; procedure defined below Then if you define the procedure converse, which uses respond, before you define respond, the compiler will not be puzzled when it reads the definition of converse. -- What have you learnt ? --------------------------------------------- Here are some revision questions to think about. Some of them are explicitly or implicitly answered in this teach file. Others may require you to look back at other teach files. You may need to ask other students, or demonstrators, or your tutor, for help. What is a variable? What is a procedure? What is the difference between a procedure and a procedure definition? What is the general form of a procedure definition? What does it mean to say that a procedure takes an argument? What does it mean to say that a procedure produces a result? What is the difference between producing a result and printing something out on the screen? How does converse give inputs to and get results from other procedures? What does readline do? What is a conditional instruction? What is the general form of a conditional instruction? What is the default case of a conditional instruction? What does matches do? How many arguments does matches require? How many results does it produce? What is "repeat" for? What is the closing bracket for repeat? How can you make a repeat loop stop? What are pattern variables? What is the difference between "??" and "^^" ? Where can "!" occur? What is the purpose of "!" ? If you don't use "!" how should you declare pattern variables? How should other local variables be declared in a procedure? How do you get Ved to insert a header for your file. How do you get Ved to insert a header above a procedure definition? Write down answers then search this file and other files to check them. -- Using trace --------------------------------------------------- The 'converse' procedure calls 'respond' and 'getproblem'. You can use the TRACE command to show the order in which the procedures are called (in this example the order is obvious, but it will not be so in later, more complicated, examples). Mark and load the following trace greet respond getproblem converse farewell; This tells POP-11 that those five procedures are to be 'traced' whenever they run. Nothing visible happens immediately. The tracing only shows itself when you run the procedures. Try running: converse(); It should produce output showing when each procedure starts and when it finishes, and if it has any inputs or outputs those are shown also. The symbol used to show a procedure starting is ">", and the symbol used to show a procedure finishing is "<". Thus the interaction might look like this > converse ** Good day. Please type your name, and finish with RETURN ? fred bloggs !> greet [fred bloggs] !< greet [Hello fred bloggs - I am here to help you] ** [Hello fred bloggs - I am here to help you] !> getproblem ** [please state your problem] ? can you help me? !< getproblem [can you help me ?] !> respond [can you help me ?] !< respond [please go on] ** [please go on] !> getproblem ** [please state your problem] ? i hate computers !< getproblem [i hate computers] !> respond [i hate computers] !< respond [perhaps you hate yourself] ** [perhaps you hate yourself] !> getproblem ** [please state your problem] ? bye !< getproblem [bye] !> farewell [fred bloggs] !< farewell [. . . . fred bloggs . . .] ** [. . . . fred bloggs . . .] < converse Thus, the line !> greet [fred bloggs] indicates that 'greet' is called with argument [joe bloggs]. The exclamation mark "!" indicates that it is already running another traced procedure (converse). The line !< greet [Hello fred bloggs - I am here to help you] indicates that the program is leaving greet, and that greet has produced as its result the list [Hello fred bloggs - I am here to help you]. This is left on the stack. Another procedure can then do something with it, e.g. print it out. Try your own conversation with tracing switched on. -- Switching off tracing ---------------------------------------------- You can switch off the tracing with untrace, i.e. give this command: untrace greet respond getproblem converse farewell; Then test converse again. converse( ); This time, the trace printing will not occur because you have UNtraced the procedures. TRACE is very useful when you are developing a complex program. It helps you check that the sub-programs are doing what you want them to. NB. If your 'output.p' file is now very long you can delete unwanted portions by marking them, and using ENTER d (see TEACH MARK) -- Further reading ---------------------------------------------------- TEACH READLINE gives more practice in using readline, and shows how to build a toy interactive teaching program for arithmetic. TEACH DEFINE gives more information about defining procedures. TEACH MATCHES explains the use of the matcher in more detail TEACH MATCHES2 gives more information about the matcher. HELP MATCHES gives a summary overview TEACH LISTS and TEACH ARROW give exercises in list processing. TEACH WHYSYNTAX explains why a grasp of syntax is required for understanding natural language. TEACH GRAMMAR Gives an introduction to formal grammars and programs that analyse the structure of a sentence. This helps to explain why the pattern matching approach used by Eliza is not adequate in general. The Pop-11 primer gives a lot more information on lists and the matcher. You can browse it online with the command: TEACH PRIMER Finding things in it requires you to use the editor search mechanism. In particular, Chapter 6 gives a lot of information about lists, and Chapter 7 about the Pop-11 pattern matcher and database. Depending on your installation, there may be a file called TEACH LOCALINDEX giving a list of locally produced teach files TEACH INDEX summarises available teach files supplied with Poplog. TEACH TEACHFILES gives an annotated list of available teach files supplied with Poplog See also the Computers and Thought book by Sharples et al. (MIT Press) You can learn from it how to go beyond Eliza to programs that use a grammar and have a deeper understanding. ======================================================================= CONTENTS - (Use ENTER g to access required sections) -- Introduction -- Running the Pop-11 Eliza Program -- When you have eliza running -- Things to try on Eliza -- Prerequisites -- The table of contents -- More specific overview of objectives for this file -- Planning the architecture of your program -- A first program to print out a response -- Load and test the procedure -- Reminder: Summary of load or compile commands in VED -- Making GREET's behaviour more varied -- Testing the new definition -- GREET explained a line at a time -- Testing GREET with different sorts of inputs -- Local variables can have their names changed -- Write your own version of GREET -- Define respond -- Work still to be done -- Outline of remaining tasks -- -- 1. Generalise respond -- -- 2. Design a controlling procedure (converse) -- A framework for converse -- Getting the user's name: defining GETNAME -- Put test commands inside comment brackets -- Combining getname and greet -- Asking the user to state a problem or ask a question -- Defining farewell -- Testing the whole of converse -- The control structure used in converse -- Making RESPOND more interesting -- Testing the 'respond' procedure -- Extending respond -- The structure of the conditional (if) instruction -- Revision exercise -- Making respond even more flexible -- How Eliza uses pattern matching -- The role of the Pop-11 pattern matcher -- Using 'matches' -- Using some of the input to build the output: pattern variables -- Making a pattern variable work in a procedure: using "!" -- Further options -- Comment on how readline works -- Coming back after logging out -- The structure of your file respond.p -- What have you learnt ? -- Using trace -- Switching off tracing -- Further reading --- $poplocal/local/teach/respond --- Copyright University of Birmingham 2009. All rights reserved. ------