518 lines
18 KiB
Plaintext
518 lines
18 KiB
Plaintext
File: MULISP2.LES (c) 12/27/85 Soft Warehouse, Inc.
|
||
|
||
CLRSCRN
|
||
This is the second of a series of on-line lessons designed to teach the
|
||
fundamentals of LISP programming using the muLISP dialect of LISP.
|
||
|
||
The first lesson explained in detail the 5 primitive functions in pure muLISP.
|
||
In this lesson you will learn how to add your own functions to the basic
|
||
primitives.
|
||
|
||
|
||
CONTINUE
|
||
Like primitive functions, user-defined functions are called with arguments and
|
||
return a single value based upon the arguments. Naturally a function must be
|
||
defined before it is called with specific arguments. Thus, there must be some
|
||
means of referring to each of the arguments within the function definition
|
||
before the actual arguments are known.
|
||
|
||
The name used within a function definition to refer to an as yet unspecified
|
||
ACTUAL ARGUMENT is called a FORMAL ARGUMENT. A list of a function's formal
|
||
arguments is included at the beginning of its definition.
|
||
|
||
The next screen will show how formal arguments are used in a definition.
|
||
|
||
|
||
CONTINUE
|
||
In pure muLISP, expressions of the following form
|
||
|
||
(DEFUN name (arg1 arg2 ...)
|
||
task1
|
||
task2
|
||
... )
|
||
|
||
are used to define the function named <name>. The atoms <arg1>, <arg2>, etc.
|
||
are the formal argument names used for referring to the function's actual
|
||
arguments. The body of the function is made up of one or more tasks. DEFUN
|
||
stands for DEfine FUNction.
|
||
|
||
We will postpone a full discussion of the component parts of function
|
||
definitions until a later lesson. For now, we will show how to define
|
||
functions by example beginning with the next screen.
|
||
|
||
|
||
CONTINUE
|
||
Some people prefer a more mnemonic name than CAR for the function that returns
|
||
the first element of a list. This situation can be easily corrected by the
|
||
following definition:
|
||
|
||
$ (DEFUN FIRST (LST)
|
||
(CAR LST) )
|
||
|
||
Note that the function being defined is named FIRST, that the function takes a
|
||
single argument, that the argument can be referred to by the name LST within
|
||
the function body, and that the function body consists of a single task which
|
||
returns the CAR of the argument.
|
||
|
||
User-defined functions are called in exactly the same way as the primitive
|
||
functions. For example:
|
||
|
||
$ (FIRST '(DOG CAT COW PIG))
|
||
|
||
|
||
CONTINUE
|
||
No need to stop with selecting the FIRST element of a list. Here is a
|
||
definition for selecting the SECOND element of a list:
|
||
|
||
$ (DEFUN SECOND (LST)
|
||
(CAR (CDR LST)) )
|
||
|
||
During this break, using the functions CAR and CDR define the function THIRD
|
||
and try out the functions FIRST, SECOND, and THIRD on some lists. When
|
||
entering multi-line muLISP expressions, like function definitions, you cannot
|
||
edit the previous line once the <RETURN> key has been pressed. Thus, make
|
||
sure each line of a definition is correct before continuing to the next. When
|
||
you are ready to continue the lesson, type (RETURN) and press the <RETURN>
|
||
key.
|
||
|
||
BREAK
|
||
Here is our definition for THIRD:
|
||
|
||
$ (DEFUN THIRD (LST)
|
||
(CAR (CDR (CDR LST))) )
|
||
|
||
$ (THIRD '(DOG CAT COW PIG))
|
||
|
||
CONTINUE
|
||
In lesson #1 we learned that the atom NIL is used to represent the empty list.
|
||
NIL was also the atom returned by the functions EQL and ATOM to indicate a
|
||
false truth value. Since NIL is clearly a very special muLISP atom, it is
|
||
important to be able to recognize it.
|
||
|
||
What we need is a recognizer function called NULL that returns T if its
|
||
argument is the empty list (i.e. NIL); otherwise it should return NIL. (From
|
||
now on we will say the "null list" rather than the "empty list".)
|
||
|
||
During this Break, define the function NULL (you only need to use one of the
|
||
5 primitive functions), and then try your new function out on various atoms
|
||
and lists.
|
||
|
||
BREAK
|
||
Here is our definition for NULL. Note that it is not necessary to quote NIL
|
||
in the definition, since the value of NIL is NIL. If you used () instead of
|
||
NIL in the definition, () does not have to be quoted.
|
||
|
||
$ (DEFUN NULL (OBJ)
|
||
(EQL OBJ NIL) )
|
||
|
||
$ (NULL '(A B C))
|
||
|
||
$ (NULL ())
|
||
|
||
CONTINUE
|
||
Up till now each user-defined function has been essentially just an
|
||
abbreviation for the single task that makes up the function body. But, the
|
||
real power of functions comes from their ability to choose between courses of
|
||
action based upon their arguments.
|
||
|
||
Currently we have at our disposal three functions that can be used for
|
||
testing arguments. They are EQL, ATOM, and NULL. Functions used for testing
|
||
are often called PREDICATES.
|
||
|
||
The next screen describes how to use predicates to make decisions within
|
||
function definitions.
|
||
|
||
|
||
CONTINUE
|
||
As you may recall, a function body consists of one or more tasks. In pure
|
||
muLISP, tasks can be divided into two classes: simple tasks and conditional
|
||
tasks. A task which is an atom or a list whose CAR (i.e. first element) is an
|
||
atom is a SIMPLE task. For example:
|
||
|
||
(CONS ATM LST)
|
||
|
||
The tasks in the functions defined thus far have all been simple tasks. A
|
||
task which is a list whose CAR is NOT an atom is a CONDITIONAL task. For
|
||
example:
|
||
|
||
((ATOM EXPN) (CONS EXPN LST))
|
||
|
||
The CAR of a conditional task is the conditional's predicate. If the
|
||
predicate returns NIL, the value of task is also NIL and evaluation of the
|
||
function body proceeds with the next task, if any. If the predicate returns
|
||
any value other than NIL, the remaining tasks in the function body are ignored
|
||
and evaluation proceeds using the CDR of the conditional task as the remaining
|
||
tasks.
|
||
|
||
The examples in the next several screens should make this clearer.
|
||
|
||
CONTINUE
|
||
We already have the function ATOM that returns T if and only if its argument
|
||
is an atom. However, pure muLISP does not have a similar primitive recognizer
|
||
function for lists. Why not? Because we can write our own if we need it.
|
||
|
||
Since our new function will be a LIST Predicate, let's name it LISTP. If its
|
||
argument is an atom, LISTP should return NIL. If the argument is not an atom,
|
||
it must be a list and LISTP should return T.
|
||
|
||
$ (DEFUN LISTP (OBJ)
|
||
((ATOM OBJ) NIL)
|
||
T )
|
||
|
||
You can think of the body of this definition as saying: "If OBJ is an atom,
|
||
return NIL; otherwise return T".
|
||
|
||
After trying LISTP out on several objects including the null list, redefine
|
||
LISTP so it returns T when given the null list.
|
||
|
||
BREAK
|
||
Here is LISTP modified to handle the null list:
|
||
|
||
$ (DEFUN LISTP (OBJ)
|
||
((NULL OBJ))
|
||
((ATOM OBJ) NIL)
|
||
T )
|
||
|
||
$ (LISTP 'DOG)
|
||
|
||
$ (LISTP '())
|
||
|
||
$ (LISTP '(DOG CAT COW))
|
||
|
||
Note that, since the call on NULL in the first line of LISTP returns T as its
|
||
value, there is no need to put a T following (NULL OBJ).
|
||
|
||
|
||
CONTINUE
|
||
If you have created a long list of names, you may want to find out if someone
|
||
is member of the list. Using the comparator EQL you can compare the name you
|
||
are looking for with each name on the list. Tentatively, you might start your
|
||
definition like this:
|
||
|
||
(DEFUN MBR (NAM LST)
|
||
((EQL NAM (FIRST LST)))
|
||
((EQL NAM (SECOND LST)))
|
||
((EQL NAM (THIRD LST)))
|
||
((EQL NAM (THIRD (CDR LST))))
|
||
...
|
||
|
||
Not only is this getting messier by the line, but there is no end to it.
|
||
After all, you had hoped to use your new function MBR on lists of arbitrary
|
||
length. So let's consider another approach.
|
||
|
||
|
||
CONTINUE
|
||
Given a name and a list, consider the following facts:
|
||
|
||
1. If the list is null (i.e. has no elements), the name is NOT a member of
|
||
the list.
|
||
|
||
2. If the name is EQL to the CAR of the list, the name is a member of the
|
||
list.
|
||
|
||
3. If the name is not EQL to the CAR of the list, the name is a member of
|
||
the list if and only if it is a member of the CDR of the list.
|
||
|
||
It is absolutely essential that you fully understand and accept the above
|
||
facts. The first two facts should be fairly straight-forward.
|
||
|
||
The third fact is slightly more subtle. It means that if a name is not equal
|
||
to the first element of a list, then to determine whether or not the name is a
|
||
member of the list, all you have to do is determine whether or not the name is
|
||
a member of the CDR of the list.
|
||
|
||
|
||
CONTINUE
|
||
Let's convert our three facts into a three step procedure for finding out if a
|
||
name is a member of a list:
|
||
|
||
1. If the list is NULL, return NIL.
|
||
2. If the name is EQL to the CAR of the list, return T.
|
||
3. Otherwise, use this procedure to find out if the name is a member of the
|
||
CDR of the list and return the value the procedure returns.
|
||
|
||
This is called a recursively defined procedure since step #3 tells us to use
|
||
the very procedure we are defining to find out if the name is a member of the
|
||
CDR of the list. The procedure can be easily transformed into a recursive
|
||
muLISP function definition as follows:
|
||
|
||
$ (DEFUN MBR (NAM LST)
|
||
((NULL LST) NIL)
|
||
((EQL NAM (CAR LST)) T)
|
||
(MBR NAM (CDR LST)) )
|
||
|
||
Use MBR to see if DOG is a member of (CAT COW DOG PIG).
|
||
|
||
BREAK
|
||
CLRSCRN
|
||
In lesson #1 we mentioned that EQL was only good for comparing atoms, since
|
||
NIL is returned if lists are compared. For example:
|
||
|
||
$ (EQL '(DOG CAT COW) '(DOG CAT COW))
|
||
|
||
Using the techniques you learned from MBR, let's define a function called
|
||
EQLIST that returns T if two lists of atoms are equal; otherwise EQLIST
|
||
returns NIL. Consider the following recursive procedure for EQLIST and
|
||
convince yourself of its validity:
|
||
|
||
1. If NULL the first list, return NULL the second list.
|
||
2. If NULL the second list, return NIL.
|
||
3. If NOT EQL the CAR of the first list to the CAR of the second, return NIL.
|
||
4. Return EQLIST the CDR of the first list to the CDR of the second.
|
||
|
||
During this break define EQLIST and try it out on some examples. If you
|
||
follow the above procedure, you will also need to define NOT, a predicate
|
||
function that returns T if its single argument is NIL.
|
||
|
||
BREAK
|
||
Here is our definitions for EQLIST and NOT and an example:
|
||
|
||
$ (DEFUN EQLIST (LST1 LST2)
|
||
((NULL LST1) (NULL LST2))
|
||
((NULL LST2) NIL)
|
||
((NOT (EQL (CAR LST1) (CAR LST2))) NIL)
|
||
(EQLIST (CDR LST1) (CDR LST2)) )
|
||
|
||
$ (DEFUN NOT (OBJ)
|
||
(EQL OBJ NIL) )
|
||
|
||
$ (EQLIST '(DOG CAT COW) '(DOG CAT COW))
|
||
|
||
Note that the definition of NOT is identical to the definition of NULL that we
|
||
defined earlier. This is because, as you will recall from lesson #1, muLISP
|
||
uses the atom NIL to designate both the null list and the truth value false.
|
||
|
||
CONTINUE
|
||
So far the user-defined functions we have written have been either selector
|
||
functions or predicates. Let's try our hand at a constructor function.
|
||
Specifically, a function for appending two lists together.
|
||
|
||
CONS should immediately spring to mind as the prime candidate for building
|
||
lists. However, here is what happens if CONS is naively called with two lists
|
||
as arguments:
|
||
|
||
$ (CONS '(DAVE JOAN AL) '(KAREN ANN JOE))
|
||
|
||
Instead of being a 6 element list, the result is a 4 element list whose first
|
||
element is a 3 element list. The result we wanted would have to be CONSed
|
||
together as follows:
|
||
|
||
$ (CONS 'DAVE (CONS 'JOAN (CONS 'AL '(KAREN ANN JOE))))
|
||
|
||
Although multiple uses of CONS gives the desired result, what we want is a
|
||
single function that returns the result when given two arbitrary length lists
|
||
as arguments.
|
||
|
||
CONTINUE
|
||
Consider the problem of defining the function APPEND in terms of the 5
|
||
primitive muLISP functions and the user-defined functions that will be defined
|
||
by the time APPEND is called. Remember APPEND itself qualifies as "a user-
|
||
defined function that will be defined by the time APPEND is called" (in other
|
||
words APPEND can be recursively defined).
|
||
|
||
Begin by breaking the problem up into cases starting with the simplest case.
|
||
Given the lists LST1 and LST2,
|
||
|
||
1. If LST1 null, return LST2.
|
||
|
||
2. Otherwise, CONS the CAR of LST1 onto the list you get by APPENDing the CDR
|
||
of LST1 onto LST2.
|
||
|
||
The next screen defines APPEND and provides more justification for the above
|
||
procedure. However, if you feel ambitious, try defining APPEND during this
|
||
break.
|
||
|
||
BREAK
|
||
CLRSCRN
|
||
$ (DEFUN APPEND (LST1 LST2)
|
||
((NULL LST1) LST2)
|
||
(CONS (CAR LST1) (APPEND (CDR LST1) LST2)) )
|
||
|
||
$ (APPEND '(DAVE JOAN AL) '(KAREN ANN JOE))
|
||
|
||
To understand this recursive definition, consider individually the two
|
||
arguments to CONS in the last line of the definition. The first argument,
|
||
(CAR LST1), is simply the first element of LST1. In the above example, the
|
||
first argument to CONS is the atom DAVE.
|
||
|
||
Assuming our APPEND is working correctly, the second argument to CONS,
|
||
(APPEND (CDR LST1) LST2), is the list resulting from appending everything but
|
||
the first element of LST1 to LST2. Note that this is an acceptable use of
|
||
recursion since each time APPEND calls itself it is with a shorter LST1 so
|
||
eventually LST1 will be the null list and the recursion will halt. In the
|
||
above example, the second argument to CONS is the list
|
||
(JOAN AL KAREN ANN JOE).
|
||
|
||
CONTINUE
|
||
Now its your turn to write some recursively defined constructor functions.
|
||
Define the function REMBER (REMove memBER) that removes only the first
|
||
occurrence of an atom from a list. For instance, the call
|
||
|
||
(REMBER 'DOG '(CAT DOG COW DOG))
|
||
|
||
should return the list (CAT COW DOG). The definition should be of the
|
||
following form:
|
||
|
||
(DEFUN REMBER (OBJ LST)
|
||
((NULL LST) ...)
|
||
((... OBJ ...) ...)
|
||
(CONS ... (REMBER ... ...)) )
|
||
|
||
BREAK
|
||
CLRSCRN
|
||
REMBER can be defined as follows:
|
||
|
||
$ (DEFUN REMBER (OBJ LST)
|
||
((NULL LST) NIL)
|
||
((EQL OBJ (CAR LST)) (CDR LST))
|
||
(CONS (CAR LST) (REMBER OBJ (CDR LST))) )
|
||
|
||
$ (REMBER 'DOG '(CAR DOG COW DOG))
|
||
|
||
If you had trouble with REMBER, you can redeem yourself by defining the
|
||
function REMBER-ALL. Instead of just the first occurrence,
|
||
REMBER-ALL removes all occurrences of an atom from a list. Thus
|
||
|
||
(REMBER-ALL 'DOG '(CAT DOG COW DOG))
|
||
|
||
should return the list (CAT COW). Hint: you need only make a small change to
|
||
REMBER to get REMBER-ALL.
|
||
|
||
BREAK
|
||
CLRSCRN
|
||
REMBER-ALL can be defined as follows:
|
||
|
||
$ (DEFUN REMBER-ALL (OBJ LST)
|
||
((NULL LST) NIL)
|
||
((EQL OBJ (CAR LST))
|
||
(REMBER-ALL OBJ (CDR LST)) )
|
||
(CONS (CAR LST) (REMBER-ALL OBJ (CDR LST))) )
|
||
|
||
$ (REMBER-ALL 'DOG '(CAR DOG COW DOG))
|
||
|
||
Note the use of indentation in the above definition to highlight the flow of
|
||
control within the definition. Although we have not stated it explicitly, it
|
||
should be clear that muLISP is a free format language (i.e. the spacing of the
|
||
atoms in lists, including function definition lists, is not critical). As you
|
||
must have discovered by now, what is critical in muLISP is the proper
|
||
balancing of parentheses!
|
||
|
||
|
||
CONTINUE
|
||
The last function we shall discuss in Lesson #2 is the constructor function
|
||
REVERSE. Its effect is simple enough:
|
||
|
||
(REVERSE '(DOG CAT COW PIG))
|
||
|
||
results in the list (PIG COW CAT DOG). But REVERSE is tricky.
|
||
|
||
During this break, see if you can define REVERSE. If you can't figure it out,
|
||
the next screen gives a hint without giving away the answer.
|
||
|
||
BREAK
|
||
Don't forget that in addition to the 5 primitives, you are free to use all the
|
||
functions you have already defined including APPEND to write REVERSE.
|
||
|
||
The next screen gives a more substantial hint.
|
||
|
||
BREAK
|
||
If you still haven't figured out how to write REVERSE, you may need to work on
|
||
your RQ (Recursive Quotient)! Using recursion, you can REVERSE the CDR of LST
|
||
by the call
|
||
|
||
(REVERSE (CDR LST))
|
||
|
||
Then to REVERSE the whole LST, all that remains is to APPEND the REVERSE of
|
||
the CDR of LST to the single element list
|
||
|
||
(CONS (CAR LST) NIL)
|
||
|
||
Give REVERSE one last try during this break.
|
||
|
||
BREAK
|
||
REVERSE can be defined as follows:
|
||
|
||
$ (DEFUN REVERSE (LST)
|
||
((NULL LST) NIL)
|
||
(APPEND (REVERSE (CDR LST)) (CONS (CAR LST) NIL)) )
|
||
|
||
$ (REVERSE '(DOG CAT COW PIG))
|
||
|
||
Although this is a logically acceptable definition of REVERSE, it is an
|
||
extremely inefficient one. This is because APPEND is called for each element
|
||
of the list to be reversed.
|
||
|
||
Let's take a whole new approach in our effort to define REVERSE more
|
||
efficiently. During this break, think about how you would reverse the order
|
||
of a stack of sheets of paper.
|
||
|
||
CONTINUE
|
||
The simplest way to reverse a stack of paper is to repeatedly take the top
|
||
sheet (i.e. the CAR) of the stack and put it on a second stack until the first
|
||
stack is empty. The second stack should start out empty.
|
||
|
||
Given a list and a null list, this is the translation of the above process
|
||
into a recursive procedure to REVERSE the first list:
|
||
|
||
1. If NULL the first list, return the second list.
|
||
|
||
2. Otherwise, CONS the CAR of the first list onto the second list and REVERSE
|
||
the CDR of the first list.
|
||
|
||
Based on this procedure, you should now be able to define an efficient REVERSE
|
||
during this break.
|
||
|
||
BREAK
|
||
REVERSE can be efficiently defined as follows:
|
||
|
||
$ (DEFUN REVERSE (LST1 LST2)
|
||
((NULL LST1) LST2)
|
||
(REVERSE (CDR LST1) (CONS (CAR LST1) LST2)) )
|
||
|
||
$ (REVERSE '(DOG CAT COW PIG))
|
||
|
||
Note that although REVERSE is defined with TWO formal arguments, it was called
|
||
with only ONE actual argument. In general, extra formal arguments are
|
||
assigned the value NIL, which is often convenient.
|
||
|
||
CONTINUE
|
||
In this lesson you have learned how to extend pure muLISP by defining
|
||
functions. The following summarizes the major concepts presented in the
|
||
lesson:
|
||
|
||
1. The parts of a definition including the function name, the formal argument
|
||
list, and the tasks comprising the function body.
|
||
|
||
2. Two types of tasks including simple tasks and conditional tasks. Based on
|
||
the value returned by a predicate function, conditional tasks are used to
|
||
make decisions when functions are evaluated.
|
||
|
||
3. The power and elegance of recursive function definitions. Recursive
|
||
function definitions are acceptable as long as the arguments in the
|
||
recursive call are closer to the termination condition.
|
||
|
||
|
||
CONTINUE
|
||
Congratulations on completing muLISP Lesson #2. Although this concludes our
|
||
discussion of pure muLISP, it by no means exhausts the potential number of
|
||
functions that can be written in this subset of muLISP. The following are few
|
||
functions you might try defining:
|
||
|
||
1. (EQUAL list1 list2) an equality comparator of <list1> and <list2>, but
|
||
unlike EQLIST, it works even if the elements of the lists are not atoms.
|
||
For example, on the list ((A B (C D)) (E F)).
|
||
|
||
2. (SUPER-REVERSE list) reverses all levels of all lists in <list>. For
|
||
example, ((A B (C D)) (E F)) goes to ((F E) ((D C) B A)).
|
||
|
||
3. (UNION set1 set2) returns the set-theoretic union of <set1> and <set2>.
|
||
(Sets are lists in which no element occurs more than once).
|
||
|
||
4. (INTERSECTION set1 set2) returns the intersection of two sets.
|
||
|
||
5. (SUBST new old list) replaces all occurrences of <old> with <new> in
|
||
<list>.
|
||
|
||
CONTINUE
|
||
$ (RDS)
|
||
|