next up previous contents
Next: Why use "hd" Up: CHAPTER.6: LIST PROCESSING Previous: Merging lists using

Lists are a derived data-type

Pairs are the primitive datatype used: conspair, front, back

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.

destpair(pair) -> (pair_front, pair_back)

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 dog
The 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;

A chain of pairs ending in [] is a list

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([]) =>
    ** nil
The system identifier "nil" can also be used to refer to the empty list:

    nil =>
    ** []

    nil == [] =>
    ** <true>

    conspair(3, conspair(4, nil)) =>
    ** [3 4]

List expressions are "syntactic sugar"

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?

Recursively chaining down list links

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_list
Note 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.

Recursing down the front and the back of a "tree"

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.

Numeric subscripts and lists

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 ....

Iterating down list links

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


next up previous contents
Next: Why use "hd" Up: CHAPTER.6: LIST PROCESSING Previous: Merging lists using



Aaron Sloman
Fri Jan 2 03:17:44 GMT 1998