dos_compilers/Microsoft muLISP-86 v51/MULISP2.LES

518 lines
18 KiB
Plaintext
Raw Normal View History

2024-07-05 17:30:14 +02:00
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)