TEACH DEFINE Revised A.Sloman Oct 1987 Revised 26 Oct 2011 Defining procedures in Pop11 ============================= This TEACH file introduces you to some fundamental programming concepts concerned with defining procedures and running them, using Pop11. It assumes that you are familiar with the editor VED, and in particular have worked through TEACH MARK and TEACH LMR, so that you know how to mark a range in the editor buffer and compile it. You should also know how to switch between different VED files, as explained in TEACH BUFFERS and TEACH SWITCHWINDOW. TEACH RIVER gives a simple introduction to some of the programming ideas presented in this file. If you find this file difficult, try going to TEACH RIVER and then come back. There are more simple examples involving programming with numbers, in TEACH ARITH A table of contents follows. Put the cursor on the section you require, below, then type: g Redo g at any time to get back to the table of contents. CONTENTS -- Introduction -- What is a procedure? -- Procedures need building blocks -- Warning messages and error messages -- Format for simple procedure definitions -- A simple example: letterhome -- Giving a procedure an input variable: greet(someone) -- What happens when the procedure -greet- is run -- How does Pop11 interpret the definition of the procedure? -- Revision questions -- What are local variables? -- So you thought you understood "->" ? -- Using the result of a procedure -- A different procedure: perim -- Procedures are more powerful than files -- Local variables again -- You must give a procedure the right number of arguments -- Defining -perim- using multiplication -- A more complicated example: total_perim -- Conclusion -- Further reading -- Introduction -------------------------------------------------------- Pop11 enables you to express instructions that refer to different sorts of objects, including numbers, words, strings, and lists. In order to perform operations on these objects Pop11 needs to have "procedures". So you need to learn how to create objects, and how to tell Pop11 to do things to the objects. Both require the use of procedures. Some procedures are built in to Pop11, e.g. procedures for adding two numbers and for building lists or examining the elements of lists. Users can define additional procedures to suit the requirements of particular tasks. In order to do this you need to learn the "syntax" of procedure definitions. Some simple examples are given in TEACH VEDPOP. Pop11 provides several different kinds of procedures with many kinds of instructions that can be built into them. This file introduces only a subset of forms that are most widely used. The "Further reading" section at the end of this file gives references to more advanced information. -- What is a procedure? ------------------------------------------------ A Pop11 procedure is a special kind of object. You can think of it as a sort of list of machine instructions stored in the short term memory of the computer. It is a bit like a file of instructions, except that it is not stored on the magnetic disc, but in the fast short term memory of the computer so that it can be got at very quickly. When you mark and load a definition like the following example introduced in TEACH VEDPOP: define test(num); return(num + 2); enddefine; Pop11 builds one of these special objects containing machine instructions. The instructions will say, in effect: 1. Get an object from the "arguments/results stack" (explained below) and call it -num-. 2. Put the value of -num-, and the number 2 on the stack and RUN the procedure called "+". It will put its result back on the stack. 3. Return (i.e. stop doing this procedure, and return to whatever was being done before, e.g. running the editor. Later you can RUN the procedure you have defined (or CALL it, or INVOKE it) by marking and loading a command like: test(55) => That tells Pop11 to put the input, namely 55, in a special place where it can be accessed by the procedure, i.e. the "stack" and then the stored instructions are run. Revision of TEACH VEDPOP: You can mark a portion of this file using the MARKLO and MARKHI keys. You can load (i.e. compile) the marked range by means of the command: lmr or, if your terminal is set up normally, by doing: CTRL-d i.e. while holding the CTRL button down tap the D button down. When you have marked and compiled the procedure definition, the set of stored machine instructions is associated with the name "test" so that you can easily tell Pop11 which procedure you want obeyed. This is unlike some languages where you have to tell the system to GO TO an instruction on, say, line 3050. To summarise: You can think of a procedure as a set of machine instructions stored in the machine's short term memory. Most procedures have names that can be used repeatedly to ask Pop11 to RUN the procedures without having to spell out the instructions every time you need to have them obeyed. -- Procedures need building blocks ------------------------------------- All interesting computer programs are complex procedures built up using simpler procedures, which in turn are built from still simpler procedures, and so on. The most basic procedures available to you are the procedures built in to the Pop11 system. These include procedures for: building lists, comparing lists with 'templates', assigning values to variables, adding multiplying or dividing numbers, storing information in a database of lists and many more. Before seeing how to build up a procedure, you need some familiarity with some of the basic building blocks. This section explains some of them. Later you will see how to put them into a new procedure of your own. The built in Pop11 procedures can be run directly without your having to define new procedures. For example, mark the following instructions then compile them: vars someone, list; ;;; declare two variables [dear mum] -> someone; ;;; assign a list to -someone- [hello ^^someone how are you] -> list; ;;; build another list using it list => ;;; print out the list The comments on the right will be ignored by Pop11. Compiling those four lines should print out in your 'output' file: ** [hello dear mum how are you] Let's look at how that all works, one line at a time. vars someone, list; This tells Pop11 that you want to use -someone- and -list- as names of objects, i.e. as variables. The semi-colon ";" tells Pop11 where the instruction ends (since Pop11 can have several instructions on one line, or can have one big instruction going over several lines). [dear mum] -> someone; The square brackets provide one way of invoking the built-in Pop11 procedures for creating lists. So [dear mum] creates a list of two words. The arrow "->" is the assignment arrow. "-> someone;" assigns the last object constructed to be the value of the variable -someone-. [hello ^^someone how are you] -> list; As before the square brackets say "build a list" and the arrow assigns it to the variable -list-. But something special goes on here. The 'double up-arrow' symbol "^^" tells Pop11 not to include the WORD "someone" but instead to get the list which is the value of the variable -someone- and put all the elements of that list (i.e. the words "dear" and "mum") into the new list. (TEACH ARROW gives lots more examples of this). To see the difference, mark and load each of these commands in turn: [hello someone how are you]=> [hello ^^someone how is ^^someone]=> -- Warning messages and error messages --------------------------------- When you use the building blocks of Pop11 things will sometimes go wrong. Mark and load the following. [hello someone how are ^^you]=> This will produce a warning message and an error message. The warning message is: ;;; DECLARING VARIABLE you because ^^you is an attempt to use the value of the variable -you- and it has not yet been declared by a command like: vars you; The error message is something like this: ;;; MISHAP - LIST NEEDED ;;; INVOLVING: ;;; FILE : /usr/local/poplog/current-poplog/pop/teach/define LINE NUMBER: 207 ;;; DOING : mishap runproc This is because [.... ^^you ....] in a list requires the variable -you- to have a list as its value, so that the elements can be spliced into the larger list. Because no value has been assigned to -you- its value is an "undef" object -- i.e. it is undefined. If you mark the offending line and compile it again, you will get the mishap message, but not the warning message, because by now the variable has been declared, by Pop11. The difference between a warning message and an error message is that after a warning message Pop11 tries to continue -- e.g. it declares the variable for you -- whereas after an error (or MISHAP) message it stops whatever it was doing because it is totally unable to continue. -- Format for simple procedure definitions ----------------------------- A special format is used for telling Pop11 to define a new procedure. (A) First the word "define" is used to say that a new procedure is being defined. (B) Immediately after "define" give the name of the new procedure, e.g.: define test (C) After the name give a pair of parentheses '()', possibly with something in between if the procedure needs some inputs (sometimes called arguments) to work on, e.g. define test(num) define silly() define isbetween(num1, num2, num3) If your procedure is to produce a result, that can be shown using "->" after the second parenthesis, e.g. define test(num) -> result define isbetween(num1, num2, num3) -> true_or_false If a procedure uses a result variable, then at least one of the instructions in the procedure must assign a value to the result. Otherwise the result produced will be meaningless. Examples will be given below. Some procedures return two or more results, which can be shown in parentheses, e.g. define solve_problem(question1, information) -> (result, explanation) that takes two inputs, and produces two outputs. Often it is a good idea to use names for inputs and outputs that indicate their roles. If there are two or more output variables each one must be assigned to inside the procedure body. (D) A semi-colon is required to say that the end of the "procedure heading" has been reached. (The semi-colon is essential to indicate the end of the heading, because in advanced programs more complex forms of heading are allowed, possibly extending over more than one line.) Examples based on the above: define test(num); define silly(); define isbetween(num1, num2, num3); define test(num) -> result; define isbetween(num1, num2, num3) -> true_or_false; define solve_problem(question1, information) -> (result, explanation); (As in the last example, a procedure header can be split across two or more lines.) (E) Then give the instructions to be executed when the procedure is obeyed. This is called the "body" of the procedure. (F) The definition ends with "enddefine". (This is the closing bracket, to match the opening bracket "define".) (G) Add a semi-colon to tell Pop11 that this is the end of the instruction to define a procedure. I.e. the format for the simplest sort of procedure definition is: define ( ) ; enddefine; or with output variables: define ( ) -> (); enddefine; For example: define join_three(arg1, arg2, arg3); ;;; heading [these arguments were supplied] => ;;; part of body [^^arg1 ^^arg2 ^^arg3] => ;;; rest of body enddefine; ;;; closing bracket You can mark and compile that, and test it thus: join_three([a], [b c], [d e f]); which should print out ** [these arguments were supplied] ** [a b c d e f] Another example, with a result variable: define mirror(list) -> palindrome; ;;; take a list of words and produce a new list containing ;;; those words followed by the words reversed. [ ^^list ^^(rev(list)) ] -> palindrome; enddefine; Mark that, compile it then test it: mirror([a b c]); If you just run that, its result is left on the Pop11 stack, and nothing will be printed. But you can run it and print the result: mirror([a b c]) => which prints: ** [a b c c b a] What will this print? mirror([eat and enjoy everything]) => What will this print? mirror(99) => try it. NOTE: Pop11 has many kinds of matching opening and closing brackets. (This is explained in more detail in TEACH BRACKETS). This is unlike some languages (e.g. LISP) which use only a few matching pairs of brackets. We think that is bad for beginners because although you have fewer syntax words to learn about, it is very easy to put an opening or closing bracket in the wrong place and then the compiler cannot give you advice as to what the mistake was, whereas the Pop11 compiler can give you messages like: FOUND endif READING TO enddefine The following procedure definitions fail to conform to the above rules for defining procedures. So if you try to mark and load them you will get error messages. Try editing them so that they no longer give errors when you mark and load them. First try them as they are so that you get used to the error messages. They will not necessarily be very clear messages in every case! define silly1 (arg1 ; arg1=> enddefine; define silly2 enddefine; define (arg1) silly3; enddefine; define silly4 arg1,arg2) [^^arg1 ^^arg2] => enddefine; define silly4(arg1, arg2) (result); [^^arg1 ^^arg2] => enddefine; The last error message may be abit misleading because pop11 thinks you intended the procedure definition to end with ")" and complains because there is no ";". It cannot tell that you left out "->" . -- A simple example: letterhome ---------------------------------------- Here is an example, using the list-building technique shown above. Type the following into a file called 'define.p' in which you can store examples from this teach file: define letterhome(); lvars someone; [dear mum] -> someone; [hello ^^someone how are you]=> enddefine; When you type that into your file, don't indent the first line. That enables you to use VED facilities that tell when a procedure definition starts by looking for "define" at the beginning of a line. (Advanced programs can use "nested" definitions which should be indented. But don't try that now.) Pop11 is a 'lower case' language, so always make sure that you type in lower case, even if some of the examples are presented in capitals for clarity. Mark and compile the definition of -letterhome- The definition merely tells Pop11 that the word 'LETTERHOME' is to be the name of a set of instructions to be stored in the short term memory when the definition is compiled. It doesn't yet tell Pop11 to obey, or execute, the instructions. Here's how you ask Pop11 to execute the procedure (obey the commands) three times. Mark and compile the three lines. letterhome(); letterhome(); letterhome(); The procedure includes "local variable" declaration. The declaration in the second line of the procedure, namely: lvars someone; (look back at the definition) specifies that the procedure needs a 'local variable' called "someone". That is, it needs to have some private storage, which is to be associated with the name "someone". The assignment arrow '->', in line 3, is used to put something into that storage area. The remaining instructions are as explained in a previous section. -- Giving a procedure an input variable: greet(someone) ---------------- The above procedure (LETTERHOME) is rather silly for a number of reasons. One is that it prints out exactly the same thing every time. In general you want the behaviour of a procedure to be more flexible and general. How it works should depend on the input it has been given. We often refer to a procedure's input as its 'argument', or 'parameter'. The term 'argument' is derived from the way mathematicians talk about the 'argument of a function'. Here is a definition of a procedure a bit like -letterhome-, except that it has one argument called "someone" and it introduces a new format, for defining procedure outputs, in the first line. define greet(someone) -> greeting; lvars someone, greeting; [hello ^^someone how are you today] -> greeting; enddefine; In this case, the top line of the definition specifies which variables are being used as names for the input and the output, namely SOMEONE and GREETING. More precisely SOMEONE is used as an 'input variable' and GREETING as an 'output variable'. Sometimes these are referred to as 'input locals' and 'output locals'. How these work will be explained below. This definition explicitly specifies in the second line that the input variable "someone" and the output variable "greeting" are to be be declared as local variables, using "lvars". However that declaration will be done automatically for input and output variables, so you can omit the lvars declaration for input and output variables, thus: define greet(someone) -> greeting; [hello ^^someone how are you today] -> greeting; enddefine; Type the definition of -greet- into your file 'define.p' (making sure it is quite separate from your definition of 'letterhome'). MARK and LOAD the procedure. You can do this in one go if you make the FIRST line of the definition start at the beginning of a line, without indentation, thus: define greet(someone) -> greeting; When you have done that you can then do the following to Load the Current Procedure. Put the VED cursor somewhere inside the procedure definition - anywhere will do, including the first or last line of the definition. Then do lcp This means "Load Current Procedure". VED searches for the beginning and end of the procedure definition then loads the whole thing without your having to mark it. You can do this even more quickly by typing: c I.e. press the ESC button then the C button. (However if VED has been set up for you so as to emulate another editor, e.g. EMACS, this may not work, and you will have to learn the appropriate command sequence from your tutor. lcp should always work, however.) If you have made a typing error you may get a mishap message on the command line. Look at the message carefully, and see where the cursor has got to in the file. The error must be before that point. Try correcting the error, and compile the procedure again. (If you can't get it to work, be SURE to ask someone for help. If you don't have a local expert, you can join the pop-forum mailing list at Birmingham university and post messages to it. https://mailman.cs.bham.ac.uk/mailman/listinfo/pop-forum ) Eventually you should get your definition compiled successfully using lmr or lcp or c. You can then test the procedure by asking Pop11 to run it with a list as input (as argument). Type the following in your 'output' file. Then mark and load the lines. (NB: lcp is not appropriate here as you are not compiling a new procedure definition. So MARK the lines, then use lmr, or CTRL-d): greet([mum]) => greet([silly computer]) => greet([lots of rubbish]) => Try all that, and variations. Look carefully at the round and square brackets. The square brackets are for making the lists. The round brackets indicate what is given as input to greet. Notice what happens if you leave out one or more of the brackets, e.g. try marking and loading the following, errors and all, and look carefully at the mishap messages that you get on the command line: greet( [mum ) => greet( silly computer] ) => greet [lots of rubbish]) => These all produce 'compile time' error messages because they are syntactically ill-formed. The second example will also produce a warning message in your output file because Pop11 thinks you are using 'silly' as an undeclared variable, before it realizes that there is a syntactic error, when it finds "]". If you give -greet- a number instead of a list, you will get a 'run time' error message. Try to mark and load the next line: greet(999) => ;;; MISHAP - LIST NEEDED ;;; INVOLVING: 999 ;;; FILE : .....(file name) and line number ;;; DOING : null dl greet runproc ... It wanted a list, not a number. Don't worry about the "DOING" line for now. -- What happens when the procedure -greet- is run ---------------------- When you type, to Pop11: greet([uncle joe]) => several things happen. The bit in square brackets actually gets done first. [uncle joe] This tells Pop11 to make a list containing the two words "uncle" and "joe". The list is then stored in a special place called the STACK. Then Pop11 interprets 'greet ( ) ' as meaning, 'obey the instructions in the procedure called "greet" (OR produce a mishap message if there isn't such a procedure)'. -greet- is expected to produce a new object as its result, which is then left on the STACK. Finally the 'print arrow' prints out what was left on the stack, preceded by two asterisks. You can tell that the procedure -greet- itself does not print out the result as follows. Mark and load the line: greet([joe]); The result is not printed out because you have not ended the command with the print arrow "=>". However when the procedure has finished, VED finds that something has been "left on the stack" and prints it on the command line. If you did not notice that do it again and look at the command line. You can get -greet- to put three things on the stack then print them in your output file in one go using "=>", if you mark and load the following, which is three separate commands: greet([fred]); greet([joe]); greet([old boy])=> Three lists will be printed in your 'output' file. -- How does Pop11 interpret the definition of the procedure? ---------- Suppose you type to Pop11: greet([uncle joe]) This makes a list [uncle joe], and puts it in a special part of the computer's memory called "the stack". It then runs the instructions stored in the procedure -greet-. How does that work? Look at the heading in the definition of the procedure -greet-. define greet(someone) -> greeting; The bit before "->" says there is an input variable called SOMEONE. So when the procedure -greet- runs it takes off the stack the list that was left there for it, and uses that as the value of SOMEONE. So when the procedure greet is run, with the command greet([uncle joe]) this starts with the equivalent of: [uncle joe] -> someone; The portion after "->" says that the variable GREETING is to be an 'output variable'. The input variable affects what happens when the procedure starts running: it takes something off the stack. The output variable affects what happens when the procedure stops running: it puts something back on the stack, namely whatever happens to be the value of GREETING. The next line of the definition: vars someone, greeting; is a 'declaration' saying that SOMEONE and GREETING are local variables, so that when they are changed inside -greet- they will not interfere with anything else which uses variables with that name. This line is not absolutely necessary here, because Pop11 automatically declares SOMEONE and GREETING as local to -greet- because they occur in the procedure heading. But it is a good idea to get used to declaring temporary variables as local. The next line is: [hello ^^someone how are you today] -> greeting; The part before "->" tells Pop11 to build a list, which contains the words between the square brackets, except that the '^^' symbol says that the elements in the list called SOMEONE should be inserted into the list, rather than the word "someone" itself. This means that the list actually created is: [hello uncle joe how are you today] The second half of the line is: -> greeting; This says that the newly created list is to be 'assigned' to become the new 'value' of the variable GREETING. I.e. GREETING becomes a name for the list: [hello uncle joe how are you today] This is its value ONLY during this run of the procedure -greet-, because the variable GREETING is LOCAL to the procedure. This use of "->" as the 'assignment arrow' is quite different from the use in the procedure heading. NB. In the body of the procedure "->" actually tells Pop11 to do an assignment. In the heading "->" tells Pop11 that GREETING is an output variable. Finally the "enddefine" is reached, so that there are no more instructions in the 'body' of the procedure. At that point instead of just finishing off, the procedure checks whether it has an 'output variable' and if so puts its value on the stack. The procedure heading for -greet- was: define greet(someone) -> greeting; So GREETING is an output variable, and therefore when the procedure finishes, the value of GREETING is put on the stack, or, as we say, -greet- 'returns' it as a result. So the list [hello uncle joe how are you today] is left on the stack when the procedure finishes. If you run the procedure with the command: greet([uncle joe]) => then the final part of this, the "print arrow" says that the result that is left on the stack should be printed out (preceded by two asterisks). Hence you'll get in your 'output' file: ** [hello uncle joe how are you today] Now test the procedure, by giving it some more inputs as a value for SOMEONE, and try to relate what is printed out, to the above explanation. N.B. be very careful about getting the round and square brackets right. Try various examples, like: greet ( [ stuart ] ) => greet([my dear friend]) => Pop11 doesn't care whether what you type makes sense, as long as you give -greet- a list as input. So you can do nonsense like: greet([66 plus 99 is 55]) => You can even give it an empty list: greet([]) => Try marking and loading that. -- Revision questions -------------------------------------------------- After running a few examples you should go back over the preceding explanation, and compare it with what you've observed. You should make notes on what you have learnt so far. You could use VED to type your summary into a file called 'define.notes'. For example, type in answers to the following questions: What is a procedure? What is the format for a procedure definition without any input or output variables? What is the format for a definition with one input variable and no output variables? What is the format for a definition with one input variable and one output variable? How do you declare the variables as local to a procedure? What do the square brackets [....] do? What is the meaning of "^^" between square brackets? How do you tell Pop11 to run a procedure with a list as input and print out the result? How can you compile the "current" procedure without using lmr What does c do? Why are you advised to type the first line of a procedure definition without any indentation? -- What are local variables? ------------------------------------------- Look again at the definition. define greet(someone) -> greeting; [hello ^^someone how are you] -> greeting; enddefine; The LVARS line has been omitted because, as explained above it isn't needed for input and output variables: they are automatically declared as LOCAL variables for the procedure. What it means to say that the variables SOMEONE and GREETING are "local" to -greet- can be illustrated by the following. We first assign values to them outside -greet- then run -greet- so that they get new values inside -greet- then check their final values: First declare the variables, give them values and print them out. Mark and load the next four lines: vars someone, greeting; [hi there] -> someone; [big boy] -> greeting; someone, greeting => That should print out two lists ** [hi there] [big boy] The line starting "vars" declares the two variables as "global". They are not defined inside a procedure definition, so they are not "local". Although the two words were used for input and output variable names inside the procedure greet, that use is totally disconnected with the use of global variables. Now run -greet- greet([uncle joe]) => This will TEMPORARILY give the local variable SOMEONE the value [uncle joe] and the local output variable GREETING the value: [hello uncle joe how are you today] and the value of the output variable should be printed out. But if you now re-do: someone, greeting => You'll see that their values have not been changed as a result of running greet. This is true of all LOCAL variables: when the procedure to which they are local has finished you cannot get at the values the variables had while the procedure was running. This enables different procedures to have the same local variables and not interfere with one another. So you can define procedures which use variable names that you find convenient, without worrying about whether that will interfere with something else. Interference can occur where variables are used without being made local, so that should be avoided wherever possible. In fact, it makes no difference at all to Pop11 what names you use for your inputs, outputs and other local variables, as long as you use them consistently and you don't try to use words like "define" that already have a special meaning to the system. As far as Pop11 is concerned, the procedure defined by: define greet(person) -> salutation; [hello ^^person how are you] -> salutation; enddefine; produces behaviour that is identical in every way with the procedure -greet- defined above. You will probably find all this a bit confusing at first. You can go back over it using the PAGEUP and PAGEDOWN buttons. (Or else use g to go back to the table of contents and see which sections you'd like to re-read.) -- So you thought you understood "->" ? -------------------------------- It is very important to notice that the use of "->" in the procedure header is different from its use inside the procedure, on the second line. In the header: define greet(someone) -> greeting; The bit '-> GREETING' does not tell Pop11 to assign anything to the variable GREETING. Rather it is a 'dummy instruction' which tells Pop11 that you are going to include an instruction somewhere in the program which will assign some object to GREETING, and whatever value GREETING has when the procedure finishes (reaches "enddefine"), is to be treated as the 'result' of the procedure. I.e. it is to be left on the 'stack', a part of the computer memory reserved for temporarily storing arguments and results of procedures. In the next line: [hello ^someone how are you] -> greeting; the last bit '-> GREETING' is a real instruction to Pop11 to assign something to GREETING. -- Using the result of a procedure ------------------------------------- When a procedure produces a result (left on the stack), you can do other things with it besides printing it out. For instance, you can associate the result with a new name. That would be a way of getting the value into GREETING outside the procedure, e.g.: greet([mum]) -> greeting; greeting => However, GREETING outside the procedure has nothing to do with GREETING inside the procedure. Thus, if you run the procedure again, with a different input, the external value of GREETING is unchanged: [hi there] -> greeting; greeting => greet([fido])=> greeting => unless you assign a new value to the external GREETING, as in: [hi there] -> greeting; greeting => greet([fido]) -> greeting; greeting => Often when you have defined a procedure it is a useful building block that can be used for constructing other procedures. The file: TEACH RESPOND shows how you can use a procedure something like -greet- as a building block in constructing a larger procedure that simulates a non-directive therapist! -- A different procedure: perim ---------------------------------------- So far we have described only procedures with at most one input. Pop11 allows you to define procedures with more than one input. For example, suppose you need to work out the perimeter of a rectangular room given its length and its breadth. If the length is 15 feet and the breadth 9 feet then you can work out the perimeter thus: 15 + 9 + 15 + 9 => Given dimensions of another room, e.g. 12 by 6, you can do the same again: 12 + 6 + 12 + 6 => If you frequently have to do this sort of thing it is useful to have a procedure that takes in the two numbers and produces the perimeter as its result. See if you can define a procedure called 'perim'. It should take two inputs and produce one result, so the heading could be something like: define perim (side1, side2) -> total; (We can't use 'length' as a variable because that is already the name of a built in Pop11 procedure, and the result will be a MISHAP message.) Try to complete that definition in your file called 'define.p' before you read on. Then mark it and compile it (or use lcp, or c to compile it.) If you get a mishap message try to correct the definition and try again. When you have managed to compile your definition test it and see if you get the following results, when you test it in your file called 'output': perim(3, 5) => ** 16 perim(23, 31) => ** 108 perim(10,10) => ** 40 DON'T READ ON TILL YOU HAVE TRIED DEFINING 'PERIM' AND HAVE TESTED YOUR DEFINITION Here is a possible answer. You could have defined perim as follows: define perim (side1, side2) -> total; side1 + side2 + side1 + side2 -> total; enddefine; What are the local variables for this version of -perim-? If you did not get your own version to work, put that one in your 'define.p' file and test it in your 'output' file. -- Procedures are more powerful than files ----------------------------- In some ways a procedure is like a file with a collection of instructions. You can store a set of instructions in a file without putting them into a procedure. But by compiling them into a procedure which has a name you not only can execute them repeatedly much more quickly than if you frequently have to LOAD the file to to get the instructions done, you can also give the procedure different inputs (arguments), to get it to do slightly different things. You saw this previously with -greet-, and can demonstrate it also with -perim-. perim(666, 99) => perim(750, 532) => -- Local variables again ----------------------------------------------- Notice that although something is assigned to TOTAL inside the procedure: define perim (side1, side2) -> total; side1 + side2 + side1 + side2 -> total; enddefine; you cannot get at the value of TOTAL after executing the procedure. This is because TOTAL is a LOCAL variable of the procedure, just as GREETING was for the procedure -greet-. If you need further convincing, try the following: total => perim(3, 2) => total => perim(99, 45) => total => So the value of TOTAL 'outside' the procedure is not altered by what happens inside the procedure. The value assigned to TOTAL inside is accessible from outside because when the procedure has finished, the value of its output local is left on the 'stack' (a special portion of the machine's memory reserved for this purpose). From there it can be picked up by an assignment to a new variable. e.g. vars x; perim(99, 45) -> x; x=> Try that. -- You must give a procedure the right number of arguments ------------- It is an important fact that the procedure PERIM requires two arguments (inputs). This is indicated in the procedure definition by the fact that there are two 'input variables' between the parentheses on the top line. Look back at it. If you provide only one input when you try to get the procedure obeyed, you'll get a mishap message. Try the following: perim(99) => ;;; MISHAP - STE: STACK EMPTY (missing argument? missing result?) ;;; DOING : perim runproc PERIM tries to take two things off the stack. One thing is put there, namely 99. So when it looks for the second thing it can't find it and causes Pop11 to print the error message and abort. You can also get an error message if you give perim the wrong sorts of arguments, e.g. words or lists: perim("five","four") => ;;; MISHAP - NUMBER(S) NEEDED ;;; INVOLVING: five four ;;; DOING : + perim runproc Notice the second line gives the clue that it found the two words, and the third line says it was doing "+" inside the procedure perim. -- Defining -perim- using multiplication ------------------------------- Can you redefine PERIM yet again, using the multiplication symbol "*", instead of only addition "+". Try modifying the definition in your file 'define.p'. Replace the dots in the following with appropriate words: define perim (side1,side2) -> total; ( ... + ... ) * 2 -> total; enddefine; I.e. add the two numbers, then multiply by 2, to produce the 'total', which will be left on the 'stack'. What should go in place of the dots? Try it and test the modified procedure. If you cannot get it to work, please ask for help. -- A more complicated example: total_perim ----------------------------- Just in case you have found all this too simple. Here is a new example showing how to use the procedure -perim- as a building block for a more complex procedure, along with Pop11 constructs that have not yet been introduced, but will hopefully be clear from the context. Suppose you have to work out how much wallpaper is needed for a set of rectangular rooms all of the same height. You are given a list of the lengths and breadths of the rooms as follows. vars roomsizes=[ [10 8] [9 7] [20 15]]; TASK: Define a procedure called total_needed which is to take a list in that form, except that it can have more than three room sizes. It is also given the height (the same for all rooms), and the width of the roll of wall-paper. Work out how much wallpaper is required, i.e. the total length. (For now ignore the problem of cutting strips of wallpaper narrower than the width of the roll.) SUBTASK: First define a procedure called total_perim which takes the list of room sizes and works out the total perimeter. define total_perim(list) -> total; vars list, room, x, y, total=0; for room in list do room --> [?x ?y]; perim(x, y) + total -> total; endfor enddefine; Compile then test this with the command: total_perim(roomsizes)=> Total_perim uses -perim- as a sub-procedure, and can itself be used as a sub-procedure in -total_needed-. Thus: define total_needed(rooms, height, paper_width) -> len; vars rooms, height, paper_width, len; total_perim(rooms) * height / paper_width -> len enddefine; Then test it, for rooms 8 feet high and wallpaper 2.5 feet wide. total_needed(roomsizes, 8, 2.5) => Do the calculations by hand to check that the result printed out is correct! You can use TRACE to see what is going on, with a different example. Mark and load the next line: trace total_needed, total_perim, perim; Find out the total length of wallpaper needed if the rooms are 9 feet high and the width of the wallpaper is 3 feet: total_needed(roomsizes, 9, 3) => >total_needed [[10 8] [9 7] [20 15]] 9 3 ;;; starting total_needed !>total_perim [[10 8] [9 7] [20 15]] ;;; starting total_perim !!>perim 10 8 ;;; perim for room 10 by 8 !!perim 9 7 ;;; starting perim again !!perim 20 15 !! ** 414 -- Conclusion ---------------------------------------------------------- You should try making notes on all the things you've learnt. Make sure you know the answers to the following questions. (Look back over this file, and if you can't find the answers, ask for help.) 1. If you are reading a help file and it tells you to define a procedure using VED, how do you tell VED you want to edit the procedure? 2. How do you then get POP to compile the procedure? 3. How do you test a procedure? 4. How do you get back to where you were in reading the TEACH file? 5. If you can't remember part of the TEACH file, how do you move the cursor back to read it again? 6. What is an output local variable? 7. What is an argument for a procedure? 8. When a procedure which has local variables is executed, what happens to their values when the procedure has finished? 9. What is the difference between an EXPLICIT and an IMPLICIT declaration of a variable as local to a procedure? 10. What is the stack used for? (See TEACH STACK for revision on this) 11. Why is it useful if after a procedure has finished, the variables which it uses as locals, are reset to their previous value? (See TEACH VARS for more on this). -- Further reading ----------------------------------------------------- The information presented here is elaborated in TEACH PRIMER There is a partly out of date book on Pop11: R.Barrett, A.Ramsay and A.Sloman Pop11: A practical language for artificial intelligence Chapters 1, 2, 3 and 6, cover some of this material with some more advanced information in chapters 11 and 14. However, that book was written before Pop11 was made to treat local variables in procedures as 'lvars', so some parts of the book are now out of date. Online information accessible via the editor -------------------------------------------- The TEACH files mentioned below have their names preceded by an asterisk. This is because VED includes a short-cut mechanism for accessing a teach or help file: type n to move the cursor to the next asterisk, and repeat until the cursor is just before the name of the file you want. Then type h, and the TEACH file (or HELP file) will be read in. If you want to go back to a previous asterisk, use N instead. TEACH * PROCEDURES elaborates on some of the ideas presented here. TEACH * VARS explains more about local and global variables. The following TEACH files provide extra practice and explanations: TEACH * VARS, * ARROW, * MATCHES, * STACK TEACH * RIVER and TEACH * RESPOND introduce mini-projects giving more practice. TEACH * TEACHFILES An overview of teach files available. HELP * DOCUMENTATION This tells you about various kinds of documentation in Poplog and how to find it. For more advanced readers: -------------------------- For more on local and global variables and scoping see HELP * VARS HELP * LEXICAL HELP * LVARS For more experienced programmers there is more on the difference between "vars" and "lvars": TEACH * VARS_AND_LVARS HELP * DEFINE summarises the information presented in this file, in a form suitable for more experienced users. REF * PROCEDURE gives an advanced overview of Poplog procedures. REF * POPSYNTAX gives a more complete summary of Pop11 syntax, but is not recommended for beginners. REF * STACK gives an advanced overview of the stack used for passing arguments and results of procedures --- C.all/teach/define --- Copyright University of Sussex 2011. All rights reserved. ----------