Just as procedure definitions can extend the range of imperatives in Pop-11, so can they also be used to extend the range of expressions. For example, here is a procedure which, when given two numbers, produces another number which is got by doubling the first two then adding.
define doublesum(num1, num2) -> total; num1 + num1 + num2 + num2 -> total; enddefine;This defines a procedure whose name is "doublesum" and which has two input (lexical) local variables, namely num1 and num2, and one output (lexical) local variable, namely total.
There are several different formats for procedure definitions, but we have now seen the two most common -- one which produces no results and one which produces a single result. The formats for these are as follows:
define <name> (<arguments>); <declarations of local variables>; <body of procedure> enddefine; define <name> (<arguments>) -> <name of result>; <declarations of local variables>; <body of procedure> enddefine;If the input and output variables are not declared explicitly, they will default to lexical variables, as if "lvars" had been used. Before Poplog version 15, the default was as if "vars" had been used.
(Experienced programmers can use a compile_mode declaration, described in the file HELP COMPILE_MODE, to control what happens to undeclared input and output locals.)
Given the above definition of doublesum, the expression
doublesum(2, 3)invokes the procedure, applying it to the numbers 2 and 3. More precisely, it first puts the two numbers on the stack, and then invokes the procedure. The procedure takes whatever inputs it requires from the Pop-11 stack, and then when finished puts its results on the stack, in this case the result of evaluating the expression 2 + 2 + 3 + 3, which gives the number 10. We can say that the whole expression "doublesum(2,3)" `denotes' the number 10.
Similarly
doublesum(4, 5)is an expression which denotes the number 18. Did you notice that DOUBLESUM is effectively the same procedure as PERIM, defined in the rooms example in chapter 1, even though its name, and the names of the variables it uses, are different? Convince yourself of the equivalence by studying them closely. Although they are different internally, they take the same sorts of arguments, and produce the same sorts of results, computed in the same way.
Generally, if a procedure produces a result then its name can be used to form an expression, by supplying it with appropriate argument expressions.
The arithmetic procedures "+" and "*" also produce results, and so by applying them to arguments we can form expressions denoting numbers:
3 + 5 3 + 5 * 2The latter denotes 13, since the multiplication is done before the addition, for reasons which will be explained later.
When we define a procedure we specify whether it needs to be given any input and whether it produces any `results'. For example the procedure silly was defined so as to take one thing as its input. We say it takes one `argument'. Doublesum was defined to take two arguments, and produce one result. Silly printed things out, using the print arrow, but that is not the same as producing a result.
When a procedure produces a result, the result is available for use by other procedures in the computer. But if something is printed out on the screen, this can't be used by other procedures, since the computer cannot see what is on the screen. Instead, results are stored for internal use in a special portion of the computer memory called the `stack'. This will be explained in more detail later. When a result is produced it can be assigned to a variable, or used as input to another procedure.
vars x; 3 + 4 -> x; ;;; assign the result of + to x. silly (3 + 4); ;;; result of + is input to silly.The word "output" can be ambiguous: referring either to something printed out, or to a result left by a procedure on the `stack'. (Later, in Chapter 3, we explain in more detail what the stack is and how it works.) When we talk about a procedure producing output the context should make clear which is intended: printing something out, or leaving a result on the stack. Students often confuse printing something out and leaving a result internally for another procedure to use, because both can be described using the word "output". Similarly the word "input" is used sometimes to refer to information typed in from a terminal or read in from a file, and sometimes to refer to the arguments handed to a procedure by another procedure within the machine.
The arithmetic procedures + and * each take two arguments (two numbers) and produce a result, one number which is left on the stack. Neither directly reads anything from the terminal nor prints anything out to the terminal, though they can be made to do so, e.g. the following could be part of an interactive session with Pop-11
: itemread() + itemread() => : 22 : 33 ** 55Where "itemread()" runs a procedure to read in an item from the terminal.
In Pop-11 a procedure can return any number of results, 0, 1, 2 or more, or even a variable number. E.g. the procedure explode, which takes a list or some other structure containing several items and produces the contained items as results, produces different numbers of results.
vars list1 = [a b c], vect1 = {1 2 3 4 5}; explode(list1) => ** a b c explode(vect1) => ** 1 2 3 4 5The procedure "dest" when given a list, returns exactly two items, the head of the list (the first item) and the tail of the list (a list of all other items).
dest(list1) => ** a [b c]The notation for defining a procedure that returns two results is a slight extension of the notation shown previously. E.g. suppose you wanted to define a procedure that when given two lists as input, list1 and list2, returned two lists as output, namely list1 concatenated with list2, and list2 concatenated with list2. You could define the procedure with two output local variables thus:
define join_two(list1, list2) -> (out1, out2); list1 <> list2 -> out1; list2 <> list1 -> out2; enddefine; ;;; Now Test it join_two([a b c], [1 2 3]) => ** [a b c 1 2 3] [1 2 3 a b c]The two outputs can be simultaneously assigned to two variables;
vars new1, new2; join_two([a b c], [1 2 3]) -> (new1, new2); new1 => ** [a b c 1 2 3] new2 => ** [1 2 3 a b c]The same sort of notation can be used for a procedure that produces three results.