459 lines
16 KiB
Plaintext
459 lines
16 KiB
Plaintext
File: MULISP4.LES (c) 12/27/85 Soft Warehouse, Inc.
|
||
|
||
CLRSCRN
|
||
This is muLISP programming lesson #4. It requires about 50 minutes to
|
||
complete. In this lesson we shall discuss the following subjects:
|
||
|
||
1. Selector functions, used to select a component part of a binary tree
|
||
structured data object.
|
||
|
||
2. Constructor functions, used to construct binary trees from simpler data
|
||
objects.
|
||
|
||
3. Iterative versus applicative programming in muLISP.
|
||
|
||
|
||
CONTINUE
|
||
In our discussion of pure muLISP we described the most basic selector
|
||
functions CAR and CDR in relation to lists. The CAR of a list is the first
|
||
element of the list. The CDR of a list is everything but the first element
|
||
of the list. For example:
|
||
|
||
$ (CAR '(MARS VENUS MERCURY))
|
||
|
||
$ (CDR (CAR (CDR '((VOLUME 90) (DIMENSIONS 3 5 6) (WEIGHT 2000)))))
|
||
|
||
Since lists are just a special way of thinking about binary trees, the
|
||
functions CAR and CDR can also be used on such trees.
|
||
|
||
|
||
CONTINUE
|
||
As mentioned in the previous lesson, the left branch of a tree is called the
|
||
CAR branch and the right the CDR branch. Thus as you might expect, the
|
||
functions CAR and CDR respectively extract the left and right branches of a
|
||
binary tree. Here are some examples of their use:
|
||
|
||
$ (CAR '(GREEN . BLUE))
|
||
|
||
$ (CDR '(GREEN . BLUE))
|
||
|
||
$ (CAR (CDR '(YELLOW . (RED . BROWN))))
|
||
|
||
During this break, extract the renewable energy sources from the binary tree
|
||
assigned to the variable ENERGY:
|
||
|
||
$ (SETQ ENERGY '((OIL . (COAL . SOLAR)) . (WOOD . NUCLEAR)))
|
||
|
||
When you are ready to return to the lesson, type (RETURN) and press the
|
||
<RETURN> key.
|
||
|
||
BREAK
|
||
CLRSCRN
|
||
In addition to CAR and CDR, compositions of CAR and CDR are primitively
|
||
defined in muLISP. These functions are named using the middle letters of CAR
|
||
and CDR to indicate the particular composition. For example, the function
|
||
CADR is equivalent to the CAR of the CDR of a list. For example:
|
||
|
||
$ (CAR (CDR '(DOG CAT COW)))
|
||
|
||
$ (CADR '(DOG CAT COW))
|
||
|
||
All other compositions of two, three, and four calls on CAR and CDR are also
|
||
primitively defined in muLISP. They are named CDAR, CAAR, CDDR, CAAAR, CAADR,
|
||
CADAR, CDAAR, etc. in the obvious manner. These functions are more efficient
|
||
than using compositions of CAR and CDR and require less typing.
|
||
|
||
During this break, redefine the functions SECOND and THIRD using the
|
||
composition functions and try them out on some examples.
|
||
|
||
BREAK
|
||
Here is SECOND and THIRD defined using the composition functions:
|
||
|
||
$ (DEFUN SECOND (LST)
|
||
(CADR LST) )
|
||
|
||
$ (DEFUN THIRD (LST)
|
||
(CADDR LST) )
|
||
|
||
$ (SECOND '(APPLE ORANGE LEMON PEAR))
|
||
|
||
$ (THIRD '(APPLE ORANGE LEMON PEAR))
|
||
|
||
|
||
CONTINUE
|
||
Thus far we have always accessed a list beginning from the left end.
|
||
Sometimes it may be necessary to access the last element of a list.
|
||
|
||
During this break, recursively define the selector function LAST-ELEMENT that
|
||
returns the last element of a list and test it out on some lists.
|
||
|
||
BREAK
|
||
This a recursive definition of LAST-ELEMENT. Note that the case where LST
|
||
is the null list must be handled as a special case:
|
||
|
||
$ (DEFUN LAST-ELEMENT (LST)
|
||
((NULL LST) NIL)
|
||
((NULL (CDR LST))
|
||
(CAR LST) )
|
||
(LAST-ELEMENT (CDR LST)) )
|
||
|
||
$ (LAST-ELEMENT '(THE QUICK BROWN FOX))
|
||
|
||
|
||
CONTINUE
|
||
Up to this point the lessons have taught the APPLICATIVE style of programming.
|
||
This style emphasizes expression evaluation, functional composition, and
|
||
recursion.
|
||
|
||
muLISP also supports the ITERATIVE style of programming. This style
|
||
emphasizes iterative control constructs, variable assignments, and sequential
|
||
processing. The function LOOP is the primary muLISP iterative control
|
||
construct. LOOP takes any number of arguments, which are sequentially
|
||
evaluated like the tasks of a function body, except:
|
||
|
||
1. After LOOP's last argument is evaluated, control returns back to the first
|
||
task in the loop.
|
||
|
||
2. When a nonNIL conditional task is evaluated in a loop, the value of the
|
||
conditional is returned as the value of the loop, and evaluation proceeds
|
||
with the task immediately following the loop, if any.
|
||
|
||
There can be any number of conditional exits anywhere within a loop. If there
|
||
is no such exit, the loop will continue indefinitely.
|
||
|
||
CONTINUE
|
||
To illustrate the use of the LOOP control construct, here is an alternative
|
||
to the recursive definition of LAST-ELEMENT given earlier:
|
||
|
||
$ (DEFUN LAST-ELEMENT (LST)
|
||
((NULL LST) NIL)
|
||
(LOOP
|
||
((NULL (CDR LST))
|
||
(CAR LST) )
|
||
(SETQ LST (CDR LST)) ) )
|
||
|
||
$ (LAST-ELEMENT '(THE QUICK BROWN FOX))
|
||
|
||
During this break, define MBR iteratively using LOOP. (MBR atom list) returns
|
||
T if <atom> is EQL to any element of <list>; otherwise it returns NIL.
|
||
|
||
BREAK
|
||
An iterative definition of MBR:
|
||
|
||
$ (DEFUN MBR (ATM LST)
|
||
(LOOP
|
||
((NULL LST) NIL)
|
||
((EQL ATM (CAR LST)))
|
||
(SETQ LST (CDR LST)) ) )
|
||
|
||
$ (MBR TED '(BOB CAROL TED ALICE))
|
||
|
||
|
||
CONTINUE
|
||
As you might suspect, muLISP has a primitively defined function, named LAST,
|
||
for accessing the right end of a list. However, unlike the function
|
||
LAST-ELEMENT, LAST returns the last cons of a list rather than the last
|
||
ELEMENT. For example:
|
||
|
||
$ (LAST '(TOKYO WASHINGTON LONDON PARIS))
|
||
|
||
LAST-ELEMENT can be defined in terms of LAST as follows:
|
||
|
||
$ (DEFUN LAST-ELEMENT (LST)
|
||
(CAR (LAST LST)) )
|
||
|
||
$ (LAST-ELEMENT '(TOKYO WASHINGTON LONDON PARIS))
|
||
|
||
During this break, guess what the LAST of the following data object is and
|
||
verify your guess by a call on LAST:
|
||
|
||
(23 54 -23 15 . 27)
|
||
|
||
BREAK
|
||
CLRSCRN
|
||
The primitive selector function NTH is useful for extracting the nth element
|
||
of a list. If <n> is a nonnegative integer, (NTH n list) returns the <n>th
|
||
element of <list>. muLISP uses ZERO BASED INDEXING to refer to the elements
|
||
of a list. Therefore, the first element (i.e. the car) of a list is referred
|
||
to as the 0th element of the list. For example:
|
||
|
||
$ (NTH 0 '(BOOK PENCIL PAPER PEN))
|
||
|
||
$ (NTH 2 '(BOOK PENCIL PAPER PEN))
|
||
|
||
During this break, use NTH to define the function INDEXER such that if <list2>
|
||
is a list of numbers, then (INDEXER list1 list2) returns a list of the
|
||
elements of <list1> corresponding to the indices in <list2>. Thus, the call
|
||
|
||
(INDEXER '(A B C D E F G) '(6 2 4 0))
|
||
|
||
should return the list (G C E A).
|
||
|
||
BREAK
|
||
Here is our definition for INDEXER and an example:
|
||
|
||
$ (DEFUN INDEXER (LST1 LST2)
|
||
((NULL LST2) NIL)
|
||
(CONS (NTH (CAR LST2) LST1) (INDEXER LST1 (CDR LST2))) )
|
||
|
||
$ (INDEXER '(A B C D E F G) '(6 2 4 0))
|
||
|
||
|
||
CONTINUE
|
||
Whereas NTH returns an element of a list, the primitive selector function
|
||
NTHCDR returns a tail of a list. If <n> is a nonnegative integer,
|
||
(NTHCDR n list) returns the <n>th cdr of <list>. For example:
|
||
|
||
$ (NTHCDR 0 '(BOOK PENCIL PAPER PEN))
|
||
|
||
$ (NTHCDR 2 '(BOOK PENCIL PAPER PEN))
|
||
|
||
$ (NTHCDR 5 '(BOOK PENCIL PAPER PEN))
|
||
|
||
|
||
Note that both NTH and NTHCDR return NIL if there are not enough elements
|
||
in the list.
|
||
|
||
CONTINUE
|
||
Next we shall discuss the primitive muLISP constructor functions.
|
||
|
||
In the pure muLISP lessons we described the constructor function CONS in terms
|
||
of building lists. CONS can also be described in terms of creating binary
|
||
trees. CONS creates a single cons, the CAR of which is the first argument to
|
||
CONS and the CDR of which is the second argument. For example:
|
||
|
||
$ (CONS 'AGE 43)
|
||
|
||
If you want some practice creating binary tree structures, during this break,
|
||
CONS together the following binary tree:
|
||
|
||
(((IBM . PC) . (APPLE . MACINTOSH)) . (TANDY . TRS-80))
|
||
|
||
BREAK
|
||
The computer company tree:
|
||
|
||
$ (CONS (CONS (CONS IBM PC) (CONS APPLE MACINTOSH)) (CONS TANDY TRS-80))
|
||
|
||
As was explained in the previous lesson, binary trees are displayed using
|
||
mixed DOT and LIST notation.
|
||
|
||
|
||
CONTINUE
|
||
Suppose we need to make a list of the values assigned to the variables
|
||
FIRSTNAME, LASTNAME, and ADDRESS. For example, if the variables were assigned
|
||
the following values:
|
||
|
||
$ (SETQ FIRSTNAME 'Jane)
|
||
|
||
$ (SETQ LASTNAME 'Smith)
|
||
|
||
$ (SETQ ADDRESS '(Honolulu Hawaii))
|
||
|
||
we can construct the desired list by multiple uses of CONS:
|
||
|
||
$ (CONS FIRSTNAME (CONS LASTNAME (CONS ADDRESS NIL)))
|
||
|
||
|
||
CONTINUE
|
||
Rather than having to call CONS for each variable, the primitive function LIST
|
||
achieves this effect more conveniently. LIST can have any number of
|
||
arguments. It returns a list of its arguments. For example:
|
||
|
||
$ (LIST FIRSTNAME LASTNAME ADDRESS)
|
||
|
||
|
||
CONTINUE
|
||
Although we defined the function APPEND in an earlier lesson, actually it is a
|
||
primitively defined muLISP function. APPEND's machine language definition is
|
||
somewhat more flexible than the user-defined definition, in that the machine
|
||
language version appends any number of lists together. For example:
|
||
|
||
$ (APPEND '(DOG CAT COW) '(SNAKE LIZARD CROCODILE) '(TROUT SALMON TUNA))
|
||
|
||
|
||
CONTINUE
|
||
The distinction between the three primitive constructor functions we have
|
||
discussed thus far often leads to some confusion. We can show the effect of
|
||
the functions CONS, LIST, and APPEND by calling them with the same argument as
|
||
follows:
|
||
|
||
$ (CONS '(DOG CAT) '(COW PIG))
|
||
|
||
$ (LIST '(DOG CAT) '(COW PIG))
|
||
|
||
$ (APPEND '(DOG CAT) '(COW PIG))
|
||
|
||
|
||
CONTINUE
|
||
In the pure muLISP lessons, we defined REVERSE efficiently by using an
|
||
ACCUMULATION variable. The resulting definition was:
|
||
|
||
$ (DEFUN REVERSE (LST1 LST2)
|
||
((NULL LST1) LST2)
|
||
(REVERSE (CDR LST1) (CONS (CAR LST1) LST2)) )
|
||
|
||
$ (REVERSE '(A B C D E))
|
||
|
||
During this break, define REVERSE iteratively using the LOOP control
|
||
construct. You can use the same accumulation variable technique.
|
||
|
||
BREAK
|
||
An iterative definition of REVERSE:
|
||
|
||
$ (DEFUN REVERSE (LST1 LST2)
|
||
(LOOP
|
||
((NULL LST1) LST2)
|
||
(SETQ LST2 (CONS (CAR LST1) LST2))
|
||
(SETQ LST1 (CDR LST1)) ) )
|
||
|
||
$ (REVERSE '(A B C D E))
|
||
|
||
|
||
CONTINUE
|
||
Often while sequentially processing the elements of a list in a loop, we refer
|
||
to the CAR of the list, then shorten the list by one. This operation is
|
||
analogous to "popping" the first element off the top of a stack.
|
||
|
||
The primitive muLISP function POP facilitates such operations. If <stack> is
|
||
a symbol whose value is a list, (POP stack) returns the CAR of the list and
|
||
sets the value of <stack> to the CDR of the list. For example, the last two
|
||
tasks in the iterative definition of REVERSE,
|
||
|
||
(SETQ LST2 (CONS (CAR LST1) LST2))
|
||
(SETQ LST1 (CDR LST1))
|
||
|
||
could be shortened using POP to the single task
|
||
|
||
(SETQ LST2 (CONS (POP LST1) LST2))
|
||
|
||
|
||
CONTINUE
|
||
Another operation commonly used while building a list within a loop is to CONS
|
||
an object onto the front of a list by an assignment of the form
|
||
|
||
(SETQ stack (CONS object stack))
|
||
|
||
The primitive function PUSH can shorten such expressions to
|
||
|
||
(PUSH object stack)
|
||
|
||
During this break, redefine REVERSE iteratively using PUSH and POP.
|
||
|
||
BREAK
|
||
Here is REVERSE defined using PUSH and POP:
|
||
|
||
$ (DEFUN REVERSE (LST1 LST2)
|
||
(LOOP
|
||
((NULL LST1) LST2)
|
||
(PUSH (POP LST1) LST2) ) )
|
||
|
||
$ (REVERSE '(A B C D E))
|
||
|
||
After having written at least four different versions of REVERSE, I hesitate
|
||
to tell you that REVERSE is actually a primitively defined muLISP function!
|
||
Naturally, the machine language version is the most efficient.
|
||
|
||
|
||
CONTINUE
|
||
When a function has completed execution and is ready to return a value, muLISP
|
||
automatically restores the environment that existed at the time the function
|
||
was called. This means that you can change the value of the function's formal
|
||
arguments without being concerned with saving the former values of the formal
|
||
arguments. For this reason, functions can be regarded as "black boxes" that
|
||
have no effect on the environment other than their returned value. If it
|
||
should be necessary for a function to return more than a single value, it can
|
||
create and return a list of the values.
|
||
|
||
Another way a function can affect the outside environment is to make
|
||
assignments within the function body to variables that are NOT included in its
|
||
formal argument list. Such variables are called "special", "fluid", or
|
||
"global" variables. The disadvantage is that it is easy to overlook such
|
||
hidden communication channels when making program changes, thus making it easy
|
||
to introduce bugs.
|
||
|
||
|
||
CONTINUE
|
||
Both the recursive and iterative definitions of REVERSE take time proportional
|
||
to the length of their argument. But for long lists, the iterative version is
|
||
perhaps 20 percent faster, depending upon the computer and amount of memory
|
||
available. However, the recursive version is more compact. When there is
|
||
such a trade-off between speed and compactness, a good strategy is to program
|
||
for speed in the most heavily used functions, and program for compactness
|
||
elsewhere.
|
||
|
||
Another consideration when choosing between iteration and recursion is the
|
||
amount of storage required to perform a given task. Each time a function is
|
||
called, addresses of the return point and the former values of the formal
|
||
arguments must be stored on a STACK so that the former environment can be
|
||
restored when the function returns.
|
||
|
||
Since recursion involves repeated nesting of function calls, a recursive
|
||
function can exhaust all available memory before completing its task. This
|
||
would invoke the "Memory Space Exhausted" error trap. The use of iteration in
|
||
such situations might permit the computation to proceed to completion.
|
||
|
||
|
||
CONTINUE
|
||
muLISP has three primitive logical functions designed to combine truth values
|
||
returned by predicate functions. The function OR takes any number of
|
||
arguments and evaluates them from left to right until a nonNIL value is
|
||
encountered or no arguments are left. If a nonNIL argument is found, OR
|
||
returns that argument's value; otherwise, OR returns NIL.
|
||
|
||
You can rely on the fact that subsequent arguments of OR will not be evaluated
|
||
after a nonNIL value is encountered. A nonNIL value does not have to be T, so
|
||
the returned value isn't restricted to T or NIL. For program control
|
||
purposes, muLISP treats any nonNIL value as T, which is a great programming
|
||
convenience.
|
||
|
||
Remember that a muLISP atom is either a symbol OR a number. Thus, the
|
||
recognizer function could be defined as:
|
||
|
||
$ (DEFUN ATOM (U)
|
||
(OR (SYMBOLP U) (NUMBERP U)) )
|
||
|
||
|
||
CONTINUE
|
||
Analogous to OR, there is a built-in AND function that takes any number of
|
||
arguments. AND evaluates its successive arguments until a NIL value is
|
||
encountered or no arguments are left. AND returns the value of the last
|
||
argument that was evaluated. Thus you can rely on the fact that subsequent
|
||
arguments will not be evaluated after a NIL value is encountered. For
|
||
example:
|
||
|
||
$ (AND (SYMBOLP 'frog) (NUMBERP 7))
|
||
|
||
CONTINUE
|
||
The primitive function NOT returns T if its argument is NIL; otherwise, it
|
||
returns NIL. For example:
|
||
|
||
$ (NOT (ATOM '(SODIUM . CHLORIDE)))
|
||
|
||
As we mentioned earlier, the definition of NOT is identical to NULL. NULL
|
||
should be used when testing for empty lists. NOT should be used when testing
|
||
the truth value returned by predicate functions.
|
||
|
||
|
||
CONTINUE
|
||
The following points summarize what we have learned in this lesson:
|
||
|
||
1. The use of the 28 primitively defined compositions of CAR and CDR (CADR,
|
||
CDDR, CAAAR, etc.) for extracting the components of binary trees.
|
||
|
||
2. The use of the functions NTH and NTHCDR to index into a list.
|
||
|
||
3. The use and distinction between the three primitive constructor functions
|
||
CONS, LIST, and APPEND.
|
||
|
||
4. Iterative programming using the LOOP control construct and the PUSH and
|
||
POP "stack" functions.
|
||
|
||
5. The logical functions AND, OR, and NOT that are used to logically combine
|
||
the truth values returned by predicate functions.
|
||
|
||
This concludes muLISP lesson #4.
|
||
|
||
|
||
CONTINUE
|
||
$ (RDS)
|
||
|