Search                        Top                                  Index
TEACH LISTS                                    Revised A.Sloman Nov 1989

                          INTRODUCTION TO LISTS
                          =====================

         CONTENTS - (Use <ENTER> g to access required sections)

 -- Introduction
 -- Prerequisites
 -- Introductory examples
 -- How to read assignment and print instructions
 -- The procedure length
 -- Length of an empty list
 -- Getting at the individual elements of a list
 -- Changing the Nth element of a list
 -- The procedure member
 -- A list can contain other lists
 -- A list has a tail, and its tail has a tail and ...
 -- An empty list has no tail
 -- A list also has a "head"
 -- Updating the first element of a list
 -- The asymmetry between hd and tl
 -- A more explicit notation using "::"
 -- Joining lists using <>
 -- Reversing a list
 -- The procedure last
 -- Miscellaneous examples
 -- Further reading

-- Introduction -------------------------------------------------------

Intelligent systems need to represent things, like objects in the world,
goals, strategies.... This requires  a 'representation language',  which
can be  used  to build  descriptions,  store them,  compare  them,  make
inferences from them.

An important  type of  object used  for building  descriptions in  POP-11
programs is the  list. You can  make lists of  words, lists of  numbers,
lists of  words and  numbers, lists  of  lists, lists  of all  sorts  of
things. In  the  exercises below  we  introduce you  to  programs  which
manipulate lists of words  and numbers, mainly.  Lists are an  important
'data type'  in POP-11  since they  can be  used for  a wide  variety  of
purposes. E.g.  programs which  understand sentences  represent them  as
lists of words,  and represent  their interpretations  as, for  example,
lists of lists of propositions.

For the  purpose of  "looking  inside" lists,  and building  new  lists,
POP-11 provides a powerful collection  of procedures. What follows  is a
brief introduction to some of these procedures.

-- Prerequisites ------------------------------------------------------

Before working through these  examples you should  be familiar with  the
editor (TEACH  VED, TEACH  VEDPOP)) and  the use  of marked  ranges  (as
explained in TEACH MARK, TEACH LMR)

-- Introductory examples ----------------------------------------------

Here is a collection  of little examples which  should give you a  basic
grasp of list  processing in POP-11.  You are advised  to make notes  on
everything as you work  through the examples.  Try the following  Pop-11
commands and see what happens:

    vars list;
    [1 2 3 4 5] -> list;
    list =>

(E.g. type that in a file, then  mark the range, and use <ENTER> lmr  to
get the commands obeyed.)


The first line declared a variable called LIST,

    vars list;

The next line assigned a list of numbers to it,

    [1 2 3 4 5] -> list;

then the third line used the print-arrow  to print out the value of  the
variable LIST:

    list =>

Now try assigning a different list of numbers:

    [1 2 3 4 5 6] -> list;

    list =>

or a list of words

    [the cat ate the mouse] -> list;

    list =>

Try assigning a list  of words and  numbers to list,  and then print  it
out.

-- How to read assignment and print instructions ----------------------

It is important to note that something like

    [the cat ate the mouse] -> list;

is really made of two separate instructions. The first is

    [the cat ate the mouse]

which means, "create the list [the cat ate the mouse] and put it on  the
stack". The stack is a part of the computer's memory reserved by  Pop-11
for communication  between  different procedures.  All  arguments  for a
procedure (i.e. inputs for  the procedure) are put  on the stack,  where
the procedure will find them, and if the procedure produces a result  it
will leave it on the stack.

The second instruction is

    -> list;

and this means, assign the last thing  put on the stack to be the  value
of the variable called "list".

The following print instruction also has two separate parts:

    list =>

The first part "list" just means put the value of the variable "list" on
the stack.

The second part "=>" means,  run the print-arrow procedure. This  prints
out whatever is on the stack.


-- The procedure length -----------------------------------------------

The procedure LENGTH, when applied to a list, counts the number if items
in the list, and returns a number as its result.

    length(list) =>

    length( [ 1 5 9 16 ] ) =>

Note the difference between the square brackets, which make a list, and
the round brackets, which function as "doit" brackets. I.e. they tell
Pop-11 to DO (or RUN or EXECUTE or OBEY) the procedure length.

You can tell Pop-11 to run the procedure without anything between the
doit brackets, but you will get a 'STACK EMPTY' error message. Try it

    length() =>
    ;;; MISHAP - STE: STACK EMPTY (missing argument? missing result?)
    ;;; DOING    :  length ......

(The error message may have some additional information. I have left
out everthing irrelevant.)

The procedure length requires an object to be given to it as its "input"
(sometimes called  its  "actual parameter"  or  its "argument").  If  an
argument is given between the doit brackets, as in

    length([ 9 8 7]) =>

Then the input (the list [9 8 7] in this case) is put on the stack,  and
that is where the procedure finds it. If it does not find it, the  STACK
EMPTY mishap message results.


-- Length of an empty list --------------------------------------------

Note that the length of the empty list is 0

    length([]) =>


-- Getting at the individual elements of a list -----------------------

Now assign a list of 5 words to the variable LIST and try:

    list(1) =>
    list(2) =>
    list(4) =>

Those commands print  out, respectively,  the first,  second and  fourth
elements of the list. Try

    list(999) =>

You will get  an error message,  because there  are not as  many as  999
elemets in the list.

-- Changing the Nth element of a list ---------------------------------

If a list contains certain elements and  you want to change one of  them
them you can "update" the list. E.g. first create a list

    [the cat ate the mouse] -> list;

check its contents

    list =>

Now make the word "dog" the second element of the list, by updating it,
using the assignment arrow.

    "dog" -> list(2);

I.e. assign "dog" to be the second element of the value of the variable
list.

(The double quote marks are required to indicate that it is the word
"dog" itself, not the value of the variable dog that is to be stored in
the list.)

Check the contents again to see what is now in the list.

    list =>

Try to make the number 6 the third element of the list, using the
assignment arrow in the same way.

Although it is often  useful to represent a  changing world by means  of
lists  whose  contents  change,  it  can  sometimes  cause  considerable
confusion if  a list  is altered  after it  has been  created, and  some
programming languages do not permit this. It is permitted in Pop-11, but
the facility has  to be used  with care, as  one part of  a program  can
change a list while another part is  intended to go on operating on  the
original list.

-- The procedure member -----------------------------------------------

If you know that a  list has the item you  want in its 4th location  you
can use  the  numerical notation  explained  above  to get  at  the  4th
element, in order to print it out, or assign it to some other variable.

If you don't know whether some item occurs in a list or not, there  is a
predicate (a procedure that  returns true or false)  built in to  Pop-11
for testing whether something  is a member of  a list. the procedure  is
called "member". It takes  two inputs, an item  and a list, and  returns
the result true if the item is in the list, false otherwise.

    member(3, [1 2 3 4 5]) =>
    ** <true>

Compare the behaviour with this one-element list:

    member(3, [ 12345 ])=>
    ** <false>

Nothing is a member of the empty list:

    member(3, []) =>
    ** <false>

    member("fox", [the fox ate the grain]) =>
    ** <true>

    member("chicken", [the fox ate the grain]) =>
    ** <false>

Note that when testing  if a word occurs  in a list we  have to put  the
word between "quote"  marks to  ensure that  member looks  for the  word
itself, and  not  the value  of  the word  treated  as a  variable.  The
procedure member is just one of many procedures available for  operating
on lists.

The procedure must be  given two arguments. If  you give it only  one, a
mishap will occur:

    member([the fac cat]) =>

    ;;; MISHAP - STE: STACK EMPTY (missing argument? missing result?)
    ;;; DOING    :  member .....

Member took one thing off the stack,  then looked for another and found
the stack empty, and complained.

-- A list can contain other lists -------------------------------------

A list may include lists among its elements, just as a bag of things may
include smaller bags of things, e.g.

    vars l;
    [ one [ list a ] two [ list b ] ] -> l;
    l=>
    l(1) =>
    l(2) =>
    l(3) =>
    l(4) =>

So the 2nd and 4th elements of L are not words, but lists containing
words. Thus we can ask for the first of the second element, or the
second of the fourth element:
    l =>
    l(2)(1) =>      ;;; get 2nd of L, then the first element of that.
    l(4)(2) =>      ;;; get 4th of L, then 2nd element of that.

Note that in  POP-11 three semi-colons mean 'ignore the rest of the line'.
I.e. they start an "end-of-line comment".

Another example:

    vars newlist;

    [[one list] [another list] [yet another]] -> newlist;

    newlist =>

The variable  newlist now  has  as its  value  a list  containing  three
elements, all of  them lists.

    length(newlist) =>

The length does not include the number of items in the lists contained
in newlist. Only the "top level" items are counted, and there are three
of them. How many elements are there in the following:

    [ a b 99 [c d e] [f g h] 10 [2 4 5] ]

Mark and copy that, and give it as input to the procedure LENGTH and see
what result it produces.

Here is how you count elements in a list like the one above

    [ a b 99 [c d e] [f g h] 10 [2 4 5] ]
      1 2 3     4       5     6    7

Note that an included list, enclosed in [ ... ] is counted as ONE  item.

You can see that the second element of a list of lists is itself a list,
thus:

    [ [a b c] [d e f g] [h i j k l] ] -> newlist;

    newlist(2) =>
    ** [d e f g]

Since this is itself a list, we can ask for its first element, i.e. the
first element of the second element of newlist is the word "d".

    newlist(2)(1) =>

Try giving a command to print the fourth element of the third element of
newlist, which should be the word "k".

We can ask how many elements, the second element of newlist contains,
thus:

    length(newlist(2)) =>

Try giving a command to print out the length of the third element of
newlist.

The procedure  member can  be asked  if a  list occurs  as a  member  of
another list:

    member([the man],   [the man on the moon]) =>
    ** <false>

    member([the man],   [ [the man] on [the moon] ]) =>
    ** <true>

    member([the man],   [ [the] [man on] [the moon] ]) =>
    ** <false>

Note the comma separating the first "argument" of member from the second
argument. The second argument HAS to  be a list. The first argument  may
be but need not be.

-- A list has a tail, and its tail has a tail and ... -----------------

One of the important  things about lists is  that they have tails  which
are also  lists. In  fact  the tail  of a  (non-empty)  list is  a  list
containing all the elements but the first.

    [a b c d e] -> list;

    tl(list) =>
    ** [b c d e]

What would the tail of the tail of the list be? Try giving a command
to print it out. Try the above with different lists.

If the length of a list is N, then the length of its tail is N - 1.

    length(list) =>
    length(tl(list)) =>

TL (read as "tail") is  a procedure which, when  given a list as  input,
produces another list as output, containing all but the first element of
the  original.  (In  some  programming  languages  the  name  "REST"  or
"BUTFIRST" would be used for this. In LISP it is called "CDR"!)

This means that if you start with a list containing only ONE item,  then
its tl must contain NO items, i.e. it must be the empty list. Try these:

    tl([cat]) =>

    tl([9]) =>

    tl([[a b c]]) =>

Note that the last one is tricky [[a b c]] is a ONE element list. It
contains one element which is a list [a b c].

In every case the tail of a one element list is the same thing, the
empty list [].


-- An empty list has no tail ------------------------------------------

If you try to find the tail of an empty list you will get a mishap
message. Try this:

    tl( [] ) =>

    ;;; MISHAP - NON-EMPTY LIST NEEDED
    ;;; INVOLVING:  []
    ;;; DOING    :  tl  ....

Note that the  "DOING" line  tells you  that it  was trying  to run  the
procedure tl. The "INVOLVING"  line tells you  what caused the  trouble,
and made tl complain that it wants a non-empty list.

-- A list also has a "head" -------------------------------------------

You've alread  seen that  writing "(1)"  can be  used to  get the  first
element of  a  list.  Alternatively  you  can  apply  the  procedure  hd
(pronounced "head") to the list.

    [the silly cat] -> list;

    list(1) =>

    hd(list) =>

What is the hd of this list?

    [[the very silly cat] [ate the mighty mouse]]

Try it.

Like the procedure tl, hd also expects a non empty list. Try:

    hd( [] ) =>

Also it complains if not given anything as input

    hd() =>


-- Updating the first element of a list -------------------------------

You can also use both formats to update the head of a list. E.g.
make "pig" the first element of list:

    "pig" -> hd(list);

check that it has changed.

    list =>

The above command is equivalent to

    "pig" -> list(1);

-- The asymmetry between hd and tl ------------------------------------

Because hd(list) and  list(1) are the  same thing you  might think  that
tl(list) and list(2) are the same thing. But they are not. Why?

Look back at the definition of the tail of a list. The crucial point  is
that whereas hd is defined to produce only the first element of a  list,
which may or may not be a list, tl always produces a list containing ALL
the elements apart from the first.

So hd([ a silly cat]) is the word  "a" and hd([6 7 8]) is the number  6,
whereas hd([[a b] [c d]]) is the list [a b]). So the result of  applying
hd to a list can be anything, depending on what the first element of the
list is.

By contrast tl always returns a list, even if it is the empty list. Try:

    tl([ a silly cat]) =>
    tl([6 7 8]) =>
    tl([[a b] [c d]]) =>

Here is one way to think of the difference between hd and tl. The square
bracket notation for creating lists may lead you to think that a list is
created from left to right. However the opposite is the case. The
instruction to create the list

    [1 2 3 4]

amounts to the  instruction first to  combine 4 with  the empty list  to
give a one element list, thus

    [4]

Then 3 is stuck on the front of that, giving a two element list

    [3 4]

whose tail is the previous list, [4]. Then yet another list is created
starting with 2 joined on to the last list, giving

    [2 3 4]

whose tail is [3 4]. Finally a list is created containing 1 as its first
element and [2 3 4] as its tail, namely the list

    [1 2 3 4]

So there  is a  sense in  which the  notation for  lists is  misleading,
because there  is  only  one  pair of  brackets  when  in  fact  several
different lists exist.

-- A more explicit notation using "::" --------------------------------

In Pop-11 the symbol "::" is the name of a procedure for creating a  new
list containing a new object as its head and an old list as its tail.
Like the symbol for the arithmetic operation "+", the symbol "::" is
an "infix" symbol, and is written BETWEEN its two inputs, rather than in
front of them (like member).

The second argument of :: MUST always be a list, possibly the empty
list.

We can now see the following equivalences

       [3]   is the same thing as      3 :: []     whose tail is []

     [2 3]   is the same thing as  2 :: (3 :: [])
                             i.e.  2 :: [3]        whose tail is [3]

   [1 2 3]   is the same thing as  1 :: (2 :: (3 :: []))
                             i.e.  1 :: (2 :: [3])
                             i.e.  1 :: [2 3]      whose tail is [2 3]

This should make clear why we use the convention that as you make a  big
list you don't need  to show all the  brackets corresponding to all  its
tails. Being able  to type  [1 2 3] is  far more  convenient,  and  much
easier to read than

    1 :: (2 :: (3 :: []))

So you can think  of :: as  if it "shoves" its  left hand argument  just
inside the  left square  bracket of  its right  hand argument,  even  if
several things have already been shoved there.

This shows how to understand the operation of hd and tl.

Think of hd as getting the last  item joined to the list, ie. the  first
item in the list. So

    hd([1 2 3])  is  hd(1 :: [2 3])   which is 1.

Whereas tl gets  what existed  before the list  item was  joined to  the
list. So

    tl([1 2 3])  is  tl(1 :: [2 3])   which is [2 3]

It should be clear from this that the second item of a list is the hd of
the tl of the list. I.e.

    list(2)

is equivalent to

    hd(tl(list))

Now try to express the 4th item of a list in terms of hd and tl. I.e.
list(4) is equivalent to ... what ???


-- Joining lists using <> ---------------------------------------------

If you already have two lists list1 and list2, and wish to make a third
list containing first all the elements of list1 then all the elements of
list2 you can use the "append" or "join" operator <>. This, like "::" is
an infix operator, written between its two arguments.

Here are some examples:

    [1 2 3] <> [a b c d] =>
    ** [1 2 3 a b c d]

(i.e. create the list [1 2 3] and  put it on the stack, create the  list
[a b c d] and put it on the stack, then run the procedure <> which takes
two things  off the  stack and  puts a  list back,  then run  the  print
procedure => which prints out what is on the stack.

See if you can anticipate what the following will print, and then try
them out.

    vars list1, list2, list;

    [a b c d] -> list1;
    [9 8 7 6] -> list2;
    list1 <> list2 -> list;
    list =>

    list2 <> list1 -> list;
    list =>

    list1 <> list1 =>

    tl(list1) <> tl(list2) =>


-- The difference between <> and :: -----------------------------------

The operator :: is more basic than <>. The instruction

    [1 2 3] <> [4 5]

is best thought of as starting with the list [4 5] and then using :: to
add 3 at the front, then :: to add 2 at the front then :: to add 1 at
the front. i.e.

    1 :: ( 2 :: ( 3 :: [4 5] ) )

I.e. the operator  <> uses the  list given as  its second argument,  but
essentially builds a new "inital sublist" copying the list given as  its
first argument.

For this reason <> MUST have a list of items on its left, if given a
list on its right. It can't join a number to a list, for example:

    3 <> [4 5] =>
    ;;; MISHAP - ILLEGAL ARGUMENTS FOR <>
    ;;; INVOLVING:  3 [4 5]
    ;;; DOING    :  <>  ....

whereas :: can

    3 :: [4 5] =>
    ** [3 4 5]

If you give a list as the first argument to :: it will keep it as a
single entity to add onto its second argument, e.g.

    [1 2] :: [3 4] =>
    ** [[1 2] 3 4]

So the new list has as its head the list [1 2]. Contrast

    [1 2] <> [3 4] =>

Always remember: :: produces a list ONE element longer than its second
argument, whereas <> produces a list whose length is the SUM of the
lengths of its two arguments.


-- Reversing a list ---------------------------------------------------

The built in procedure REV, can be given a list as input and produces
a new list as output, containing the same elements but in reverse order.
Note that the original list is unchanged.

    vars x;
    [1 2 3 4] -> list;
    rev(list) =>
    ** [4 3 2 1]

The result of rev can be assigned to another variable:

    rev(list) -> x;
    x =>
    ** [4 3 2 1]

Notice that the instruction 'rev(list) -> x' is actually made of THREE
separate instructions.
    (a) put the value of list on the stack
    (b) run the procedure rev (which may be composed of many
        instructions, and which will put its result on the stack)
    (c) assign what is left on the stack to the variable x.

Work out what each of the following should print out, and then check
your reasoning by giving the command:

    rev(rev(list)) =>

    rev(list)(1) =>
    rev(list)(4) =>

    rev(tl(list)) =>
    tl(rev(list)) =>

    vars list1, list;
    [a b c] -> list1;

    list1 <> rev(list1) -> list;
    list =>

-- The procedure last -------------------------------------------------

Can you anticipate what these commands will do:

    vars num;
    [a b c d e] -> l;
    length(l) -> num;
    num =>
    l(num) =>

This method can be used to get at the last element of a list. However it
is rather clumsy  and the procedure  last is provided  to access, or  to
update, the last element of a list. Try this:

    last(list)=>
    last(rev(list)) =>

    99 -> last(list);
    list =>

    "cat" -> last(list)
    list =>


-- Miscellaneous examples ---------------------------------------------

Notice what happens if you don't leave spaces between the numbers
when you type in a list:

    [1234] -> x;
    x(1) =>
    tl([1234]) =>

    length([1234])=>
    length ([1 2 3 4]) =>
    tl([1 2 3 4]) =>

Before trying each of these write down what you think will be
printed as a result:
    [c a T](1) =>
    [cat](1) =>
    tl([c a t]) =>
    tl([cat]) =>

    length ([c a t]) =>
    length ([cat]) =>
    [cat on mat](1) =>

    [c a t o n m a t](3)=>
    [cat on mat](3) =>

    vars x;
    [[one two three]] -> x;

How many elements in x?
    length(x) =>

The tail of x is empty ie. there are no elements after the first
    x =>
    tl(x) =>

And x has no second element:
    x(2) =>
    ;;; MISHAP - BAD ARGUMENTS FOR INDEXED LIST ACCESS
    ;;; INVOLVING:  2 [[one two three]]

-- Revision -----------------------------------------------------------

Before proceeding, it  is a  good idea to  make sure  that you  remember
everything you've read so far. You should, for example, be able to write
explanations of what the following are:

    hd
    tl
    length
    rev
    last
    member
    ::
    <>

and what the following do:

    [ 1 2 [ a b c ] d e ]  -> list;
    list(1) =>
    list(3) =>
    list(3)(1) =>

-- Further reading ----------------------------------------------------

See TEACH * STACK for more on the stack.

To learn more  on lists and  pattern matching, see  the following:

TEACH * ARROW
TEACH * MATCHES
TEACH * LISTSUMMARY

There are two files that attempt to explain in more detail how lists are
represented in the computer's memory.

TEACH * BOXES
TEACH * WAL

To learn about procedure definitions, see

TEACH * DEFINE
TEACH * VARS

and thereafter have practice defining procedures that operate on lists,
as indicated in

TEACH * SETS
TEACH * SETS2

--- C.all/teach/lists
--- Copyright University of Sussex 1989. All rights reserved. ----------