LISTS in Pop-11 are built out of a data type called PAIRS, which are two-element records. Pairs can be created using the procedure conspair.
vars pair1 = conspair(3, "cat"); pair1 => ** [3|cat]Note how pairs are printed almost like lists, except for the vertical bar to indicate that they are not two element lists.
The built in system procedures front and back can be used to access or update their elements. E.g.
front(pair1) => ** 3 "dog" -> back(pair1); pair1 => ** [3|dog]Pairs can be chained together, e.g. by creating a new pair whose back is the old one.
vars pair2 = conspair(2, pair1); pair2 => ** [2 3|dog]Notice how the printing procedure starts printing pair2 as if it were a list, that is a chain of pairs, then suddenly it finds that the list ends in a pair that has a word as its back, rather than another pair or the special end of list object [], so it indicates that it's not a proper list, by using the vertical bar again.
The procedure destpair, when given a pair, puts both the front and the back on the stack. So using destpair can sometimes be more efficient than first calling front then back.
destpair(pair1) => ** 3 dogThe front and the back of the second pair created above, pair2, can also be accessed in one Pop-11 instruction, using destpair:
destpair(pair2) => ** 2 [3|dog]For example, a loop doing something to every item in a chain of pairs might have:
while ispair(chain) do destpair(chain) -> (item, chain); .... instructions involving item ..... endwhile;instead of
while ispair(chain) do front(chain) -> item; .... instructions involving item ..... back(chain) -> chain; endwhile;
The chain of two pairs created above involving pair1 and pair2 is not yet a proper list, since every list must end with the empty list [] as the back of the final pair (except for dynamic lists, described below.)
vars pair1 = conspair("b", "c"), pair2 = conspair("a", pair1);So neither pair1, nor pair2 is a proper list, as is indicated by the way they are printed, e.g.:
pair2 => ** [a b|c]If we now change the back of pair1, by assigning [] to it, we'll get a proper list:
[] -> back(pair1); pair1 => ** [b]and this has also changed pair2 into a properly terminated list.
pair2 => ** [a b]NOTE: The empty list [] is a special, unique object, with its own dataword (like termin and popstackmark).
dataword([]) => ** nilThe system identifier "nil" can also be used to refer to the empty list:
nil => ** [] nil == [] => ** <true> conspair(3, conspair(4, nil)) => ** [3 4]
From this, it should be clear that the list expression [a b] is actually just "syntactic sugar" for the expression:
conspair("a", conspair("b", []))and [a
^
x b] is syntactic sugar for
conspair("a", conspair(x, conspair("b", [])))and [a %x + y% b] is syntactic sugar for
conspair("a", conspair(x + y, conspair("b", [])))So, inside a list expression, words are quoted by default and "
^
" or "%"
is used to unquote them, whereas outside a list words are by default not
quoted and """ is used to quote them. The same applies to vector
expressions using { ... }
Exercise: What is [the cow is brown] syntactic sugar for?
Because all lists end in the unique object [], there are many programs that chain down a list made of pairs by assigning the first pair to a variable, then the second pair, then the third, and so on, stopping only when it is found that the variable points to [].
For example here is a recursive procedure to copy a list (which could be written more compactly but would be less clear):
define copy_list(oldlist) -> newlist; lvars newtail ; if oldlist == [] then [] -> newlist elseif ispair(oldlist) then ;;; recursively copy the back of the list copy_list(back(oldlist)) -> newtail; ;;; and create a new list using the old front ;;; and the new tail conspair(front(oldlist), newtail) -> newlist else mishap('LIST NEEDED', [^oldlist]) endif enddefine; copy_list([a b c d]) => ** [a b c d]Recursive procedures can often be understood more easily if traced, using the trace mechanism described in Chapter 4 (or HELP TRACE). So try running the previous command after first doing:
trace copy_listNote that the Pop-11 system procedure copylist does exactly what copy_list does, so there's no need for the user to define copy_list.
Note also that if there were an embedded list, it would not be copied: it would reappear in the new list, as in [a b [c d] e]. Lists like this have a "tree" structure, as shown by the boxes diagrams below.
Complete copying of a tree can be achieved by a "doubly recursive" procedure, which copies list elements that are lists as well as copying the "top level" list.
For example, here's a doubly recursive version, which simply leaves its results on the stack instead of using an output local variable:
define copy_tree(tree); if ispair(tree) then ;;; copy the front if its a pair, otherwise use it, and ;;; then put the result in a new pair, with a copy of the ;;; old back conspair( if ispair(tree) then copy_tree(front(tree)) else tree endif, copy_tree(back(tree)) ) else ;;; just return the item that is not a pair tree endif enddefine; vars tree = [[a 1 2] [b 3 4] [c [x y] 5 6] d]; copy_tree(tree) =>Running that example with copy_tree traced, will show all the recursive calls.
We turn now to showing in more detail how the static lists are actually represented.
We have already seen on numerous occasions that lists can be treated as if they were one dimensional arrays, whose elements can be accessed via numerical subscripts, starting from 1.
vars list = [a bird in the hand]; list(2) => ** bird "fish" -> list(2); list(2) => ** fish list => ** [a fish in the hand]If the number is too small or too large an error will result.
list(8) => ;;; MISHAP - BAD ARGUMENTS FOR INDEXED LIST ACCESS ;;; INVOLVING: 8 [a fish in the hand] ;;; DOING : compile ....
Here is a rather different program that uses iteration rather than recursion. It searches down the list links until if finds a target element and then returns a copy of the rest of the list, starting from the target.
define tail_list(target, oldlist) -> newlist; oldlist -> newlist; repeat if newlist == [] then mishap('TARGET NOT IN LIST', [^target ^list]) else if front(newlist) = target then return(); ;;; result is newlist else ;;; get next link in the list back(newlist) -> newlist endif endif endrepeat enddefine; tail_list("cat", [The black cat sat on the mat]) => ** [cat sat on the mat]Exercise: re-write that procedure using the for loop format:
for <var> on list do .... endfor