vars num other_nums; if [one two three four five] matches [?num ??other_nums] then num ==> other_nums ==> endif; ** one ** [two three four five]Technically, the first element of a list is known as the `head'. The list of remaining elements is known as the `tail'. And there are in fact built-in procedures that extract the head and tail of a list. The built-in procedure `hd' extracts the head. The built-in procedure `tl' extracts the tail. Thus
hd(nums) ==> ** one tl(nums) ==> ** [two three four five]Being able to split a list into its head and its tail opens up a new way of writing certain types of procedure. We can illustrate this with the following definition of a procedure that adds up all the numbers in a list.
define addup(list) -> total; if list = [] then 0 -> total; else hd(list) + addup(tl(list)) -> total; endif; enddefine;The weird thing about this procedure is that it calls itself! (See the middle part of the fifth line.) But concentrate to begin with on the parts that are easy to understand. Note how the condition in the `if' checks whether the input `list' is empty and assigns 0 to `total' if it is. This means that if we call the procedure with an empty list it will certainly return 0.
addup([]) ==> ** 0But if we call the procedure with a list of at least one element then the condition `list = []' will not be satisfied. This means that the `else' part of the `if' will be executed. This contains a single command which adds the first item of the list to the result of applying the `addup' procedure to the tail of the list. As we noted, this is rather strange since the procedure is effectively calling itself as a subroutine. But it turns out to be a perfectly valid approach. And it does work!
addup([3]) ==> ** 3Because the tail of the list is empty, the result produced when `addup' calls itself is just 0 (see the explanation above). So the `+' just adds 3 (the first element of the list) to 0 to produce 3. This is assigned to the output variable `total' and becomes the final result of the original call.
Whenever the procedure is given a non-empty list, it always ends up adding the head of the list to the result of calling `addup' on the tail. Because we know that `addup' will get the answer right when given an empty list we know that it must get the answer right when given a list of one element. And if it gets the answer right when given a list of one element, it must get it right when given a list of two elements. And if it gets the answer right when given a list of two elements, it must get it right when given a list of three elements. And so on. We can test it out by giving it a list of eight elements.
addup([34 522 8766 12 14 2 0 8]) ==> ** 9358Experiment: load the definition given above and then test it out on a list of your own.
As is often the case, the task of reversing a list is most easily accomplished by building up a new list, repeatedly adding in elements to a new list that is initially empty. (This involves using hats and double hats to add new items to an existing list.) A procedure to do this the usual way (using a looping command) might be defined as follows.
define reverse(list) -> newlist; vars item; [] -> newlist; for i from 1 to length(list) do list(i) -> item; [^item ^^newlist] -> newlist; endfor; enddefine;This works OK.
reverse([1 2 3]) ==> ** [3 2 1]But a recursive definition of the `reverse' procedure might look like this.
define reverse(list) -> new_list; vars x rest, reversed_rest; if list matches [?x ??rest] then reverse(rest) -> reversed_rest; [^^reversed_rest ^x] -> new_list; else [] -> new_list; endif; enddefine;The procedure contains a `vars' command and a single `if'. The `vars' sets up two variables: `x' and `rest'. The condition in the `if' is that the value of `list' must match the pattern [?x ??rest]. If `list' matches ok then we will have the first element in the variable `x' and the remaining elements (as a list) in `rest'. With these two components the procedure can construct the reverse of the whole list by a recursive step. First it gets the reverse of the `rest' and puts it into the variable `reversed_rest'. Then it tacks `x' on to the end of this by building a new list with `reversed_rest' merged in at the beginning and the value of `x' inserted at the end.
If the list does not match the pattern, then it has to be an empty list. And in this case, it is just an empty list that should be returned. (The reverse of an empty list is just an empty list.)
The confusing thing about all this is that the procedure obtains the reverse of `rest' by calling itself. But everything works out fine. POP-11 keeps each call of a particular procedure entirely separate. So there is no interference whatsoever between two calls of the same procedure. The `list' variable of the original call is completely separate from the `list' variable in the new call. They are effectively two different variables. Ditto for the other two local variables in the procedure: `x' and `rest'. Thus there is no interference between the different calls of the procedure, even when the call is executed by the procedure itself.
Due to the way `reverse' calls itself with the tail of the list passed in, the original list gradually gets broken down until eventually we just have an empty list left. The reverse of an empty list is just an empty list, and this is indeed the value produced when an empty list is passed in to `reverse'.
The `reverse' procedure can be used to demonstrate the effect. First we execute the `trace' command by typing the word `trace' followed by the name of the procedure we are interested in.
trace reverse;This command is a statement: it produces no value---it just tells POP-11 to print information whenever `reverse' is called. The result is that calling reverse now generates a lot of extra printing.
reverse([1 2 3 4]) ==> > reverse [1 2 3 4] !> reverse [2 3 4] !!> reverse [3 4] !!!> reverse [4] !!!!> reverse [] !!!!< reverse [] !!!< reverse [4] !!< reverse [4 3] !< reverse [4 3 2] < reverse [4 3 2 1] ** [4 3 2 1]Everything here except the final line (the one beginning with `**') is `trace printing'. The first line after the original call begins with `> reverse'. This is POP-11 telling us that there has been a call on `reverse' and that it is therefore entering the procedure. After the word `reverse' we see `[1 2 3 4]'. This is the list-value passed-in as an argument.
The next line is almost the same except that it begins with `!> reverse' and ends with `[2 3 4]'. This information tells us that there has been a new call on `reverse'. The single exclamation mark tells us that the call has been made within a previous call of a traced procedure. Thus we are one level `deep'. Because the `reverse' procedure chops off and applies a recursive call to the tail of the list the input variable value is shown as `[2 3 4]'.
And so it continues all the way down until a call is issued in which the input variable has the value [ ]. At this point no further recursive calls are issued. The value returned by the procedure is just the value that was passed in, i.e., [ ]. The process then unwinds with each call tacking the head that it extracted to the end of the value it gets back from the recursive call. When the original call exits, we have the list in reverse form.
Experiment: take a few minutes to study the trace printing for the call on `reverse'. Once you have everything clear, apply the `trace' command to the `addup' procedure defined above; i.e., do
trace addup;Then call `addup' on a list of your own choosing and see what trace printing is produced. Once you are quite certain you have understood the way recursive procedures work, you can move on to do the exercises below.
get_usages([[Freda 25 0.1][Jacky 2 0.2][Jacky 1 0.2][Freda 8 0.15]]) ==> ** [[Jacky 0.6] [Freda 3.7]]Note that the procedure should not make any assumptions about the names appearing in the list. (Hint: you may find it easier to first write a procedure `get_usage_for(name)' and then use this as a subroutine.)
get_biggest_usage([[Jacky 0.6] [Freda 3.7][Bill 1.2]]) ==> ** [Freda 3.7]
trace tells POP-11 to display calls of specified procedure(s) rev built-in procedure that reverses a list