Sei sulla pagina 1di 63

File: MULISP1.

LES (c)

12/27/85

Soft Warehouse, Inc.

CLRSCRN This is the first of a series of on-line lessons designed to teach the fundamentals of LISP programming using the muLISP dialect of LISP. muLISP is a powerful programming language rich in control constructs and functions for operating on user created data structures. However there is a small subset of muLISP, hereafter called pure muLISP, that illustrates many of the fundamental programming concepts required to understand muLISP. The first two programming lessons are devoted to teaching pure muLISP. A systematic presentation of the full power of muLISP begins with the third lesson. CONTINUE muLISP is a symbolic programming language. Most conventional programming languages are primarily designed for numerical processing. Rather than manipulating numerical data structures (like numbers and arrays), muLISP programs typically manipulate symbolic data structures (like words and sentences). In muLISP virtually any symbolic data structure can be represented as an object. We will begin these lessons by describing what constitutes an object and how objects can be used to represent symbolic data structures. After that, we will describe how to use muLISP to manipulate these objects. CONTINUE Data objects can be either simple or composite. Simple data objects are called atoms. In pure muLISP, atoms are indivisible in the sense that they cannot be divided into sub-components. Like people, atoms are referred to by name. Atom names consists of a string of characters. The following are four typical atom names: APPLE RABBIT 54321 -41

Composite data objects are called lists. Lists are made up of zero or more objects, each of which can be simple or composite. Lists are entered and displayed with their elements enclosed in parentheses. The following is a list consisting of four elements: (THE QUICK BROWN FOX)

CONTINUE We stated earlier that muLISP is a symbolic programming language because it manipulated symbolic data structures. muLISP is also a functional programming language because functions are used to manipulate the symbolic data structures. Given an ordered set of data objects, a muLISP function will return a unique value based upon the data objects it is given. Pure muLISP provides 5 primitive functions for manipulating the primitive data objects (i.e. atoms and lists). Writing muLISP "programs" consists of defining additional functions to perform some desired task. We will begin the lessons by showing the mechanics of interacting with muLISP. Then we examine the use of each of the 5 primitive functions. Once you have mastered the use of these primitives, you will be ready to begin lesson 2 to learn how to define your own functions in terms of the primitive functions. CONTINUE In some ways interacting with muLISP is much like using a pocket calculator. First you enter an expression following the dollar sign prompt, then muLISP reads the expression, evaluates it, and displays the result. To prevent the evaluation of an expression, you must precede the expression with a SINGLE QUOTE MARK (also called the apostrophe or accent mark). Do not confuse this with the BACK ACCENT mark found on some keyboards. The following shows what happens when the atom name APPLE is entered: $ 'APPLE If an atom name is entered without the quote mark, the value of the atom is returned. In pure muLISP atoms evaluate to themselves. Therefore the quote mark before atoms is often not necessary: $ APPLE To enter atom names of your own, select the Break option by pressing B. When you are ready to return to the lesson, following the dollar sign prompt, type (RETURN) and press the <RETURN> key. BREAK CLRSCRN Lists are entered by enclosing the elements within matching parentheses. The following shows how to enter a list: $ '(CAT COW DOG PIG)

The elements of a list can themselves be lists. For instance, the following list might be part of a data structure describing someone: $ '((HEIGHT 72) (WEIGHT 175) (EYES BLUE) (HAIR BLOND)) If you should forget to precede the list with a single quote mark, you will invoke an "Undefined Function" error. If this should occur, select the Continue option by pressing C and the error will be ignored. Press B and try entering some lists of your own. When you are ready to return to the lesson, type (RETURN) and press the <RETURN> key. BREAK CLRSCRN LISP is an acronym for LISt Processing language. Thus as you might expect, muLISP provides primitive functions for operating on lists of objects. One of the most common operations is the extraction or selection of one the members of a list so that it can be examined independently. Functions that perform this service are called selector functions. Two of the five primitive pure muLISP functions are selector functions. The CAR function returns the first element of a list. The CDR (pronounced could-er) function returns everything except the first element of a list. The nonmnemonic names of these two functions is derived from the original implementation of LISP on an IBM 704 computer in the early 1960's. As we shall see in later lessons, these function names turn out to be convenient despite their now irrelevant origin. CONTINUE In muLISP, functions calls are made using the format (name arg1 arg2 ...) where <name> is the function's name, <arg1> is the first argument, <arg2> is the second, etc. Note that the left parenthesis precedes the function's name rather than following it as is customary in mathematics and other programming languages. This notational peculiarity alone has probably caused more grief for the novice LISP programmer than any other single thing. However, as you become more familiar with LISP the justification for this unusual notation will become apparent and seem quite natural.

CONTINUE Remember that the function CAR returns the first element of a list. Rather than say "the first element of" a list, most LISP programmers say "the CAR of" a list. The following example shows how to find the CAR of the list (DOG CAT COW PIG): $ (CAR '(DOG CAT COW PIG)) During this Break use the function CAR to determine the CAR of the list ((RAM 256) (ROM 64) (DISK 322)). Be sure to i) type the function CAR using all capital letters, ii) precede the list with a single quote mark, and iii) balance your parentheses. muLISP ignores extra RIGHT parentheses so you can include a few extra at the right end of an expression. When you are ready to return to the lesson, type (RETURN) and press the <RETURN> key. BREAK CLRSCRN The function CDR returns everything but the CAR of a list. The phrase "the CDR of" a list is used instead of the more cumbersome "everything but the first element of" a list. The following example shows how to find the CDR of the list (DOG CAT COW PIG): $ (CDR '(DOG CAT COW PIG)) During this break use the function CDR to determine the CDR of the list ((HEIGHT 72) (WEIGHT 175) (HAIR BLOND)). When you are ready to return to the lesson, type (RETURN) and press the <RETURN> key. BREAK CLRSCRN Up to now the arguments given to functions have been quoted objects. However, there is no reason the value returned by one function call cannot be used as the argument to another function call. Consider how to select the second element of a list. The CDR of a list is a list consisting of everything but the first element of the original list. Thus the CAR of the CDR of a list is the second element of the list. The following shows how to find the second element of the list (DOG CAT COW PIG):

$ (CAR (CDR '(DOG CAT COW PIG))) Using combinations of CAR and CDR extract from the list ((HEIGHT 72) (WEIGHT 175) (HAIR BLOND)) the sublist (WEIGHT 175) [note that (WEIGHT 175) is the second element of the list]. Then try to extract from the same list just the atom 175 [note that 175 is the second element of the second element of the list]. BREAK In case you had trouble, here is how to get the 175: $ (CAR (CDR (CAR (CDR '((HEIGHT 72) (WEIGHT 175) (HAIR BLOND)) )))) CONTINUE CAR and CDR are called selector functions because they are used to select or extract the parts of an object. Constructor functions perform the complementary operation of constructing composite objects from simpler objects. One of the five primitive pure muLISP functions is the constructor function CONS. CONS is used to add objects to the front of an existing list. Its first argument is the object to be added; its second argument is the existing list. For example: $ (CONS 'DOG '(CAT COW PIG)) Try adding (EYES BROWN) to ((HEIGHT 72) (WEIGHT 175) (HAIR BLOND)). Make sure you balance the parentheses around each argument to CONS. BREAK CLRSCRN Note that the CAR of the result returned by CONS will always be the first argument to CONS. Similarly the CDR of the result will always be the second argument. For example: $ (CAR (CONS 'DOG '(CAT COW PIG))) $ (CDR (CONS 'DOG '(CAT COW PIG))) See if you can CONS the CAR of the list (A B C) onto the CDR of the list (X Y Z) resulting in the list (A Y Z). Again make sure you balance the parentheses around each argument to CONS. BREAK

The correct answer to the last problem is: $ (CONS (CAR '(A B C)) (CDR '(X Y Z))) CONTINUE In order to make some sense out of muLISP programs it is essential that you learn to "read" function compositions such as the one above. This example should be read as "the CONS of the CAR of the list (A B C) onto the CDR of the list (X Y Z)". Use muLISP to find the CONS of the CAR of the CDR of the list (A B C) onto the list (X Y Z). Don't forget to balance those parentheses. BREAK The correct answer to the last problem is: $ (CONS (CAR (CDR '(A B C))) '(X Y Z)) CONTINUE As mentioned earlier, a muLISP data object is either an atom or a list. Also we explained that the CAR of a list is the first element of the list. What then is the CAR of an atom? Although it is a well-defined operation that does not cause an error, in pure muLISP CAR should not be called with an atomic argument. For this and other reasons, we need some way of recognizing whether an object is an atom or a list. The fourth primitive muLISP function we shall discuss is the recognizer function ATOM. ATOM is a function of one argument. If its argument is an atom, ATOM returns the atom T for true; otherwise ATOM returns the atom NIL for false. For example: $ (ATOM 'APPLE) $ (ATOM '(DOG CAT COW PIG)) Use the ATOM function to see whether numbers are atoms or not. BREAK CLRSCRN Consider the following ATOM test: $ (ATOM '()) As should be evident by the above example, the empty "list" is not a list at

all, but an atom! In fact, muLISP represents the empty list by the atom NIL. Thus: $ '() Try using muLISP to find the CDR of the single element list '(APPLE). BREAK CLRSCRN The last primitive pure muLISP function is EQL. It is a comparator function useful for determining if its two atomic arguments are the same atom. For example: $ (EQL 'ROM 'RAM) $ (EQL (CAR '(DOG CAT COW)) (CAR (CDR '(HORSE DOG PIG)))) Using the functions CAR, CDR, and EQL, see if the weight specified in the list ((HEIGHT 72) (WEIGHT 175) (HAIR BLOND)) [i.e. the second element of the second element of the list] is 175. Note that there is no need to quote numbers in muLISP, although it is acceptable. BREAK Here is how to check the weight equal to 175: $ (EQL 175 (CAR (CDR (CAR (CDR '((HEIGHT 72) (WEIGHT 175) (HAIR BLOND))))))) CONTINUE As stated when EQL was introduced, EQL is useful only for comparing atomic arguments (this statement is not always true in "impure" muLISP). If given lists as arguments (except of course the empty list, which is an atom anyway), EQL will always return NIL. For example: $ (EQL '(DOG CAT COW) '(DOG CAT COW)) One of the projects in the next lesson will be to define a function called EQLIST that is useful for determining the equality of two lists. CONTINUE In this lesson we have discussed what constitutes muLISP atoms and lists. Collectively these objects are used to make up the symbolic data structure that muLISP functions operate on. Pure muLISP provides the following five primitive functions for operating on objects:

1. (CAR list) the selector function that returns the first element of <list>; 2. (CDR list) the selector function that returns everything but the first element of <list>; 3. (CONS object list) the constructor function that returns the list whose CAR is <object> and whose CDR is <list>; 4. (EQL atom1 atom2) the comparator function that returns T if <atom1> and <atom2> are the same atom, otherwise it returns NIL; 5. (ATOM object) the recognizer function that returns T if <object> is an atom, otherwise it returns NIL. Congratulations on successfully completing muLISP lesson #1! In lesson #2 you will learn how to write your own muLISP functions. CONTINUE $ (RDS) 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. 2. 3. 4. If NULL the first list, return NULL the second list. If NULL the second list, return NIL. If NOT EQL the CAR of the first list to the CAR of the second, return NIL. 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 userdefined 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) File: MULISP3.LES (c) 12/27/85 Soft Warehouse, Inc.

CLRSCRN This is muLISP lesson #3. Estimated completion time is 50 minutes. The first two lessons taught the fundamentals of muLISP programming using pure muLISP. Everything you learned about pure muLISP is of course true of full muLISP; however, there is much that was left unsaid. With this lesson we shall commence describing the full capabilities of muLISP.

In Lesson #3 we will examine each of the three primitive muLISP data objects in detail. This lesson is primarily informative in nature and does not provide a lot in the way of interesting problems for you to solve. However, it does provide an essential foundation for the later lessons. CONTINUE There are three types of primitive data objects in muLISP: Symbols, Numbers, and Conses. muLISP provides numerous functions for recognizing, comparing, combining, and operating on these primitive data objects. This allows you to construct complex data structures that can accurately model in the computer virtually any real world problem. Therefore, we begin our discussion of muLISP with a description of the three basic data objects. As you may recall, pure muLISP has only two types of data objects: Atoms and Lists. In full muLISP, atoms are further subdivided into Symbols and Numbers. In full muLISP, lists are only a subset of the more general structures called binary trees made up of Conses. CONTINUE The first type of data object we shall discuss is the symbol. Associated with each muLISP symbol are 4 attributes: 1. Print name string: A unique string of ASCII characters used by the system to identify the symbol on input and to display the symbol on output. A symbol's print name string cannot be changed. 2. Current value: A symbol's value can be any muLISP data object, including itself. The default value of a symbol is the symbol itself. 3. Property list: The property list contains the symbol's property values indexed on property keys. See the muLISP lesson on property values for more information about using properties. The default property list of a symbol is the null list. 4. Function definition: A symbol's function definition is applied to the arguments given when the symbol is called as a function. The default function definition invokes the "Undefined Function" error break. CONTINUE The recognizer function SYMBOLP returns T if its single argument is a symbol;

otherwise it returns NIL. For example: $ (SYMBOLP 'XYZ) $ (SYMBOLP 41) $ (SYMBOLP '(DOG CAT COW)) During this break, see whether the null list, (), is a symbol or not. To return to the lesson, type (RETURN) and press the <RETURN> key. BREAK CLRSCRN As discussed in an earlier lesson, the comparator function EQL is used to determine if two symbols are the same. For example: $ (EQL 'APPLE (CAR '(APPLE ORANGE LEMON))) CONTINUE Since blank spaces, parentheses, and some other special characters have special significance to the muLISP, a quoted string must be used to create a symbol with such characters in its print name string. Simply enclose the string containing such characters within double quotes to create such a symbol. For example: $ "This is a (single) symbol!" Normally the double quotes around symbols containing special characters are NOT displayed when the symbol is output. (Note: Double quote marks are automatically displayed around such symbols if the value of the control variable PRIN1 is NIL.) The empty string, entered as "", is also a symbol. For example: $ (SYMBOLP "") CONTINUE The double quote mark itself can be included in a print name string by entering a backslash and a quote mark for each desired quote mark in the string. For example: $ "She said, \"I am learning muLISP.\"" When entering symbols using double quote marks, it is essential to balance the quote marks. After entering a quote mark, muLISP will continue to think you

are entering a single symbol until a matching quote mark is read. During this break, see what happens when you enter a symbol with an unmatched double quote mark and press the <RETURN> key. To return to the lesson, you will need to balance the quote marks so you will get the dollar sign prompt. BREAK CLRSCRN A symbol can be assigned a value. The value can be any muLISP data object including itself. The default value of most muLISP symbols is the symbol itself. SET is the primitive function used to set the value of a symbol (SET's first argument) to a value (SET's second argument). Of secondary importance is the fact that SET returns its second argument as its value. For example: $ (SET 'FRUITS '(APPLES ORANGES LEMONS PEARS)) $ FRUITS During this break, SET the symbol VOWELS to a list of the 5 vowels in the alphabet. BREAK CLRSCRN Generally the first argument to SET will be a quoted symbol. Rather than having to explicitly quote the symbol, the function SETQ (SET Quote) can be used instead. SETQ automatically quotes its first argument but NOT its second. For example, rather than using SET to assign a list to 'VOWELS, you could use SETQ as follows: $ (SETQ VOWELS '(A E I O U)) During this break, use SETQ to add the letter Y to VOWELS by CONSing it onto the current value of VOWELS. BREAK The following adds Y to the list of vowels: $ (SETQ VOWELS (CONS 'Y VOWELS)) CONTINUE The second type of muLISP data object we shall discuss are Numbers. Numbers are further subdivided into integers and ratios. Integers are entered as a contiguous series of digits, optionally preceded by a minus sign.

Since the value of a number is the number itself, there is no need to quote numbers: $ 41 $ -75 The comparator function EQL is used to test for the equality of two numbers: $ (EQL 3 4) $ (EQL 0 -0) CONTINUE Ratios can be entered using either decimal or slash notation (i.e. as two series of digits separated by a decimal point or a slash character, optionally preceded by a minus sign). By default, ratios are displayed using decimal notation: $ 3/4 $ -0.34 $ (EQL 0.4 2/5) CONTINUE If the control variable *POINT* is NIL, muLISP displays ratios using slash notation instead of decimal notation. Note that ratios are automatically reduced to lowest terms. Also ratios having a denominator of one are automatically converted to integers. $ (SETQ *POINT* NIL) $ -5/7 $ 0.33333333 $ 12/9 $ 5/1 CONTINUE

If the control variable *POINT* is zero or a positive integer, muLISP displays ratios using decimal notation to a maximum of POINT digits: $ (SETQ *POINT* 3) $ 2/3 $ (SETQ *POINT* 7) $ 2/3 CONTINUE The primitive recognizer function INTEGERP returns T if its argument is an integer; otherwise it returns NIL. For example: $ (INTEGERP 100) $ (INTEGERP 'FIVE) $ (SETQ PRIMES '(2 3 5 7 11)) $ (INTEGERP (CAR PRIMES)) CONTINUE The primitive recognizer function NUMBERP returns T if its argument is a number (i.e. either an integer or ratio); otherwise it returns NIL. For example: $ (NUMBERP 100) $ (NUMBERP 457.23) $ (NUMBERP -23/7) During this break, use NUMBERP and SYMBOLP to see if a sequence of digits enclosed in double quotes (e.g. "137") is a number or a symbol. BREAK The following two tests show that "137" is a symbol rather than a number: $ (NUMBERP "137") $ (SYMBOLP "137")

CONTINUE Symbols and numbers are collectively called ATOMs to suggest their indivisibility by ordinary means. The primitive recognizer function ATOM returns T if its argument is an atom (i.e. either a symbol or a number); otherwise is returns NIL. For example: $ (ATOM 'APPLE) $ (ATOM 123) $ (ATOM '(DOG CAT COW)) $ (ATOM '()) CONTINUE Sometimes you may wish to refer to a symbol itself rather than its value. In muLISP, the apostrophe character is used as a quote mark to suppress the evaluation of a symbol. For example: $ (SETQ FOO 1492) $ FOO $ 'FOO Note that the apostrophe is different from the "back accent" or "accent grave" character, `, which also occurs on some terminals. During this break, use SETQ and the quote mark to set the value of FOO back to itself (i.e. make it be an auto-quoted symbol). BREAK The following restores the value of FOO to be itself: $ (SETQ FOO 'FOO) $ FOO CONTINUE The third primitive muLISP data object we shall discuss are Conses. A cons is a nonatomic data object that points to two other data objects. The name "cons" comes from the constructor function CONS discussed in the earlier

lessons. Data can be stored in the computer's memory. The location where a data item is stored is called its ADDRESS. An address is analogous to a street address on a mailbox. The data stored there is analogous to mail in the mailbox. As with mailboxes, the contents of computer memory can change over time. Suppose we wish to represent the cons consisting of the symbol BILBO and his age 31. We could store the symbol BILBO beginning at location 7, his age 31 at location 2, and beginning at location 4 we could store a cons consisting of the pair of addresses 7 and 2: Address: 1 2 3 4 5 6 7 +-----+-----+-----+-----+-----+-----+-----+--Contents: | | 31 | | 7 | 2 | |BILBO| +-----+-----+-----+-----+-----+-----+-----+--CONTINUE muLISP manages the specific placement of data within memory automatically, so all we care about is the specific primitive symbols and numbers together with the way they are connected. muLISP keeps track of addresses such as 7 and 2 in our example, but for us they are a distraction. The following "box-car" representation of a cons suppresses such irrelevant detail: +-----+-----+ | . | . | +-/---+---\-+ / \ / BILBO \ 31

CONTINUE If you have seen one box-car, you have seen them all. So, to reduce clutter, I henceforth represent conses by a dot at the vertices in my diagrams. For example: . /\ / \ BILBO 31

Since each cons has exactly two "branches", such diagrams are called binary trees.

CONTINUE Although a linked collection of conses is best envisioned by humans as a binary tree structure, a linearized representation of conses is more suitable for a computer programming language. One of the linear representations recognized by muLISP is the so-called DOT notation. In this notation, a cons is represented by a left parenthesis, a data item, a period, a data item, and a right parenthesis. For example, the cons . /\ / \ BILBO 31

is represented in DOT notation as: (BILBO . 31) CONTINUE The left element of a cons is called the CAR of the cons. The right element is called the CDR of the cons. The elements of a cons can be any data object including another cons. For example, BILBO's last name can be included in our binary tree as: . /\ / . /\ / \ BILBO BAGGINS The equivalent dot notation representation of this tree is: ((BILBO . BAGGINS) . 31) CONTINUE Let's add the fact that BILBO is a hobbit to our binary tree: . /\ / \ \ 31

. /\ / . /\ \ 31

HOBBIT

/ \ BILBO BAGGINS Before continuing think how this three cons binary tree would be represented using dot notation. CONTINUE The tree is represented in dot notation as: (((BILBO . BAGGINS) . 31) . HOBBIT) An alternative binary tree structure for this information is the one corresponding to: ((BILBO . BAGGINS) . (31 . HOBBIT)) Sketch the corresponding binary tree diagram on a piece of paper, then hold it close to my face so I can check it out. _____ / \ >| O.O |< | \=/ | \___/ CONTINUE Oh well, my eyes must be getting bad. It should have looked something like this: . /\ / \ / \ . . /\ /\ / \ / \ BILBO BAGGINS 31 HOBBIT From this discussion it should be clear that linked conses can be used to represent virtually any tree structured data.

CONTINUE It is often most natural to represent a collection of conses as a LIST of data objects rather than as a deeply nested binary tree. For example, the elements of a set are usually displayed as a list. muLISP represents a list as a linked collection of conses whose CAR cells point to the members of the lists and whose CDR cells point to the next cons. The linked list is terminated by a CDR cell that points to NIL. For example: . /\ / \ object1 . /\ / \ object2 . . . /\ / \ objectN NIL CONTINUE When this binary tree is rotated 45 degrees counterclockwise, it is easier to see why it can be used to represent a list of data objects: .--------.--- . . . ---.----- NIL | | | | | | object1 object2 objectN The linear structure of lists suggests an external printed representation that is much more readable than the equivalent dot notation representation. Thus muLISP will automatically display the above object using LIST notation: (object1 object2 ... objectN) rather than using dot notation: (object1 . (object2 . ... (objectN . NIL) ...)) CONTINUE The muLISP object display functions use list notation where possible and dot notation where necessary. Thus, a structure of the form . /\

/ \ object1

. /\ . . /\ / \ objectN atom

/ \ object2 .

where <atom> is not the symbol NIL, is displayed in a mixed notation as: (item1 item2 - - - itemN . atom) CONTINUE The muLISP input reader function accepts list notation, dot notation, and mixed notation. Moreover, any of the elements of a list can themselves be either lists or more general expressions. The following examples show how muLISP displays a few expressions: $ '(DOG . (CAT . (COW . PIG))) $ '((AGE . 34) . (HOBBIES . (SWIMMING . THINKING))) CONTINUE During this lesson we have described the three primitive muLISP data objects: Symbols, Numbers, and Conses. The following are the main points to remember: 1. Symbols: Each symbol has an associated print name string, a value, a property list, and a function definition. Symbols containing special characters can be entered using quoted strings. SETQ is the function most commonly used to assign a value to a symbol. 2. Numbers: A number is a positive or negative rational number. NUMBERP is used to recognize numbers. Numbers are subdivided into integers or ratios. INTEGERP is used to recognize integers. 3. Conses: Conses are used to form binary tree structures that can be represented using dot notation, list notation, or mixed notation. This concludes muLISP lesson #3.

CONTINUE $ (RDS) 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) File: MULISP5.LES (c) 12/27/85 Soft Warehouse, Inc.

CLRSCRN This is muLISP programming lesson #5. This lesson introduces the primitive muLISP numerical functions and presents several techniques useful for writing efficient mathematical functions. muLISP provides the user with both infinite precision integer arithmetic and adjustable precision rational arithmetic. This means that the only limit on the size of integers is available computer memory. Integers consisting of thousands of digits are possible. This makes muLISP useful for investigations in the fields of number theory and cryptography. By default the precision muLISP uses for rational arithmetic provides about 7 digits of accuracy. This approximates the accuracy of single precision floating point arithmetic. The description of the function PRECISION in Chapter 5 of the muLISP Reference Manual provides details on how to increase the precision used for rational arithmetic. CONTINUE We begin by discussing the primitive numerical functions muLISP provides. The functions +, -, *, and / denote addition, subtraction, multiplication, and division respectively. In most conventional programming languages, you enter numerical expressions using infix notation (i.e. operators appear between their operands). For example: 3*4 + 5 Since LISP is a functional programming language, functional notation is more natural and consistent for numerical expressions. For example, the above expression is entered as: $ (+ (* 3 4) 5) During this break, familiarize yourself with these four numerical functions. When ready to return to the lesson, type (RETURN) and press the <RETURN> key.

BREAK CLRSCRN One advantage of functional notation over operator notation is that you are not limited to two operands for each operation. The functions +, *, -, and / can accept a variable number of arguments. For example: $ (+ 45 -23 57) $ (* 1 2 3 4 5) During this break using test cases, empirically determine what the functions - and / do when called with more than two arguments. BREAK CLRSCRN If a numerical function is inadvertently called with nonnumeric arguments, the "Nonnumeric Argument" error message will be displayed on the console followed by the errant function call and the option prompt Abort, Break, Continue, Top-level, Restart, System? You then must select one of the five options by entering its first letter. The description of the function BREAK in Chapter 5 of the muLISP Reference Manual describes the options in detail. The next screen summarizes the options available. CONTINUE Summary of the error break options: Abort: aborts execution, returns control directly to the current level of the read-eval-print loop, and prompts the user for further input. Break: temporarily suspends execution and prompts the user for input. The errant function can be determined by examining the value of the variable BREAK. To return from the break, type (RETURN expn) where <expn> is the value you want to be returned as the value of the errant function call. Continue: continues execution with the errant function call used as the value returned. Top-level: aborts execution, returns control to the top-level read-evalprint loop, and prompts the user for further input. Restart: abandons the current muLISP environment and starts up a fresh muLISP system.

System: terminates muLISP and returns to the host operating system. CONTINUE Enough of errors, let's return to mathematics! The factorial of a number is of great importance in probability theory. The factorial of a nonnegative integer N, denoted N!, can be recursively defined as follows: 0! = 1, N! = N*(N-1)! if N > 0 The equivalent muLISP definition of FACTORIAL is: $ (DEFUN FACTORIAL (N) ((EQL N 0) 1) (* N (FACTORIAL (- N 1))) ) $ (FACTORIAL 5) This is an efficient definition; however, if N is large there is the possibility of a memory space exhausted error because of a stack-overflow. During this break, write and test an iterative version of FACTORIAL. Hint: you will need an accumulation variable for the result. BREAK An iterative definition for FACTORIAL and an example: $ (DEFUN FACTORIAL (N % Local: % RSLT) (SETQ RSLT 1) (LOOP ((ZEROP N) RSLT) (SETQ RSLT (* N RSLT)) (SETQ N (- N 1)) ) ) $ (FACTORIAL 100) As the example illustrates, muLISP can handle very large numbers. In the definition we introduced the primitive recognizer function ZEROP. (ZEROP N) is equivalent to (EQL N 0) but is slightly more efficient. CONTINUE A series that keeps turning up in nature in the strangest places is the Fibonacci Series. The Nth Fibonacci number, denoted F(N), can be recursively defined as follows:

F(0) = 1, F(1) = 1, F(N) = F(N-1) + F(N-2) if N > 1. The equivalent muLISP definition of FIBONACCI is: $ (DEFUN FIBONACCI (N) ((ZEROP N) 1) ((EQL N 1) 1) (+ (FIBONACCI (- N 1)) (FIBONACCI (- N 2))) ) $ (FIBONACCI 10) During this break, gain a feel for the efficiency of the above algorithm by calling FIBONACCI with progressively larger arguments. BREAK CLRSCRN As you test cases should have demonstrated, this is an extremely inefficient algorithm. The problem is that to compute the Nth Fibonacci number, the (N-2)th Fibonacci number is unnecessarily computed twice, the (N-3)th three times, the (N-4)th five times, and it keeps gets getting worse. Since this is a recursive algorithm, most people jump to the conclusion that recursion is the problem, and set about writing a messy iterative definition to achieve efficiency. But the problem is not recursion but the algorithm. Rather than computing the Nth Fibonacci number by working backward toward zero, the efficient way is to start at zero and work up to the Nth Fibonacci number using two variables to store the two most recently computed Fibonacci numbers. During this break, use this approach to define an efficient, yet recursive, definition for Fibonacci numbers calling it FIB. Compare the efficiency of FIB with FIBONACCI. BREAK An efficient, recursive definition for Fibonacci numbers: $ (DEFUN FIB (N F1 F2) ((ZEROP N) F1) (FIB (- N 1) (+ F1 F2) F1) ) $ (FIB 10 1 0) $ (FIBONACCI 10)

FIB is a function of 3 arguments. The first argument is N, the second must be 1, and the third 0. If you insist on a single argument Fibonacci function, you can define a "front-end" function that merely calls FIB with the appropriate second and third arguments. For those of you still not convinced of the elegance and efficiency of recursion, write an iterative definition for Fibonacci numbers and compare it to the above definition. If you are a believer, you can simply continue on. CONTINUE Raising an expression to an integer power is certainly an important mathematical operation. To raise N to Mth power all you have to do is multiply N times itself M times. During this break, write an iterative definition of POWER as a function of two arguments. BREAK A iterative definition of POWER: $ (DEFUN POWER (N M % Local: % RSLT ) (SETQ RSLT 1) (LOOP ((ZEROP M) RSLT) (SETQ RSLT (* N RSLT)) (SETQ M (SUB1 M)) ) ) $ (POWER 2 10) The call to the primitive function SUB1 in the above definition is equivalent to, but slightly more efficient, than the call (- N 1) would be. ADD1 is also a primitively defined muLISP function. There is an even more efficient way of computing powers of numbers than this iterative technique. On to the next screen! CONTINUE Consider the following facts: 1. If M is an even number, then N to Mth power is equal to N squared raised to the M/2th power. 2. If M is odd, then N to Mth power is N times N raised to the (M-1)th power.

To implement this algorithm you will need the primitive recognizer function EVENP. It returns T if and only if its argument is an even integer; otherwise it returns NIL. During this break, define POWER using the recursive squaring approach described above. BREAK An efficient, recursive definition of POWER: $ (DEFUN POWER (N M) ((ZEROP M) 1) ((EVENP M) (POWER (* N N) (/ M 2)) ) (* N (POWER N (SUB1 M))) ) $ (POWER 10 100) CONTINUE The primitive function TRUNCATE is useful for converting ratios and quotients to integers. If <n> is a number, (TRUNCATE n) truncates <n> to the nearest integer in the direction of zero: $ (TRUNCATE 7/3) $ (TRUNCATE -0.95) If called with two arguments, TRUNCATE returns their truncated integer quotient. Note that (TRUNCATE n m) is equivalent to (TRUNCATE (/ n m)): $ (TRUNCATE 7 3) $ (TRUNCATE -46.3 5.2) CONTINUE TRUNCATE returns the truncated integer quotient of its two arguments. The primitive function REM returns the corresponding remainder of its two arguments. TRUNCATE and REM are defined so they observe the law between quotients and remainders: If (TRUNCATE n m) returns the integer q and (REM n m) returns the number r, then n = q*m + r

Often it is useful to obtain both the quotient and remainder at the cost of only one division. The primitive function DIVIDE returns cons of the quotient and remainder of its two arguments. $ (TRUNCATE 7 3) $ (REM 7 3) $ (DIVIDE 7 3) CONTINUE The Greatest Common Divisor (GCD) of two integers is the largest nonnegative integer number that evenly divides both integers. Euclid's algorithm for the GCD of the integers N and M can be paraphrased as: 1. If N = 0, then GCD (N, M) = M; 2. Otherwise, GCD (N, M) = GCD (M mod N, N). During this break, define the function GCD using Euclid's algorithm and try it out on some examples. Use the function REM to obtain M mod N. BREAK Recursive definition of GCD using Euclid's algorithm: $ (DEFUN GCD (N M) ((ZEROP N) M) (GCD (REM M N) N) ) $ (GCD 21 56) Actually the functions GCD and LCM (Least Common Multiple) are primitively defined in muLISP. Naturally the primitive versions are faster and can accept an arbitrary number of arguments. CONTINUE Finally we need to mention the primitive numerical comparator functions: =, /=, <, >, <=, and >=. For example: $ (= 34 34.0) $ (/= 3/4 0.75) $ (< 67 45) ;Equal test ;Not equal test ;Less than test

$ (>= 19 17 17)

;Greater than or equal test

As the last example shows, the numerical comparator functions can be called with more than two arguments. If called with nonnumeric arguments, these functions will cause a "Nonnumeric Argument" error break. CONTINUE To determine if a number lies within a given interval, the function < can be called with 3 arguments. For example, to determine if "g" is a lower case letter enter: $ (< 96 (ASCII 'g) 123) The function ASCII returns the ASCII code if given a character name. It returns the equivalent ASCII character if given a number between 0 and 256. During this break, write the recognizer function LOWERCASE that determines if a character is a lower case character. BREAK The LOWERCASE recognizer function: $ (DEFUN LOWERCASE (CHAR) (< 96 (ASCII CHAR) 123) ) $ (LOWERCASE 'g) CONTINUE Let's finish off this lesson by writing a function for sorting a list of numbers into increasing order. We are not too concerned with efficiency so let's use a simple "insertion sort" algorithm that is adequate for short lists. First we need a function that inserts an number in the proper place in a sorted list of numbers. During this break, write INSERT-NUM, a function that inserts NUM into LST. Use iteration or recursion depending on your taste. BREAK A recursively defined INSERT-NUM function: $ (DEFUN INSERT-NUM (NUM LST) ((NULL LST) (LIST NUM) ) ((< NUM (CAR LST))

(CONS NUM LST) ) (CONS (CAR LST) (INSERT-NUM NUM (CDR LST))) ) $ (INSERT-NUM 12 '(5 9 11 14 19 21)) During this break, use INSERT-NUM to write the function NUMBER-SORT that sorts a list of numbers. BREAK A recursive defined NUMBER-SORT function: $ (DEFUN NUMBER-SORT (LST) ((NULL LST) NIL) (INSERT-NUM (CAR LST) (NUMBER-SORT (CDR LST))) ) $ (NUMBER-SORT '(34 23 -14 27 56 22 83)) The built-in function SORT uses an efficient list merge sort. If <test> is a comparator function, (SORT list test) sorts and returns <list> based on <test>. For example: $ (SORT '(34 23 -14 27 56 22 83) '<) This concludes our discussion of numerical programming techniques using muLISP. Congratulations on successfully completing lesson #5. CONTINUE $ (RDS) File: MULISP6.LES (c) 12/27/85 Soft Warehouse, Inc.

CLRSCRN This is muLISP programming lesson #6. In this lesson we will provide muLISP with both line-drawing and "turtle" graphics capabilities. First we will define a function for plotting points on the computer screen. Next we will write a routine for drawing straight lines across the screen. Finally, we will use a polynomial approximation to define functions for finding the sine and cosine of an angle. Once these steps have been accomplished, it is relatively easy to implement turtle graphics. The LOGO computer language has popularized the idea of using turtle graphics to teach children to program. LOGO was first implemented in LISP and it

remains a close cousin. Turtle graphics is based on the idea of a "turtle" that has a heading and a position on the graphics screen. Figures are drawn by issuing commands to the turtle, which draws a line as it moves around the screen. CONTINUE muLISP is available for a variety of computers and terminals, many of which do not support high resolution graphics. The systems that do support graphics have widely varying graphics protocols. Consequently, to make this lesson applicable to the greatest possible number of systems, the lesson begins by implementing "character graphics" using only ASCII characters positioned using the cursor addressing function (SET-CURSOR row column). If you are running on a computer type that supports cursor positioning, the definition for the function SET-CURSOR is built-in. Otherwise, you must either define your own SET-CURSOR function. If SET-CURSOR is working correctly, the command (SET-CURSOR 0 0) will position the cursor at the upper left corner of the screen. During this break, make sure that SET-CURSOR is working correctly. BREAK CLRSCRN We will assign the global variable ROWS to be the number of rows of characters on the computer screen. COLUMNS is the number of columns. Thus the command (SET-CURSOR (SUB1 ROWS) (SUB1 COLUMNS)) should position the cursor at the lower right corner of the screen. If the following assignments are inappropriate for your computer screen, correct them during this break: $ (SETQ COLUMNS 80) $ (SETQ ROWS 24) BREAK CLRSCRN Rather than using rows and columns, the position of a point on a graphics screen is specified by giving it X and Y coordinates relative to an origin. The X-coordinate of a point is the horizontal distance from the origin to the point; it is positive if the point is to the right of the origin, negative if to the left. The Y-coordinate of a point is the vertical distance from the

origin to the point; it is positive if the point is above the origin, negative if below. Y | | ------+------ X | | Coordinates are normally written as a pair of numbers between square brackets; the first is the X-coordinate, the second is the Y-coordinate. For example the origin of the coordinate system is the point at [0 0]. Generally, the center of the screen is chosen as the origin for graphics routines. CONTINUE SET-CURSOR uses the upper left corner of the screen as its origin and it is called with coordinates in the opposite order to that used in a graphics coordinate system. Thus we define the function DOT that plots a character at a specified coordinate on the screen: $ (DEFUN DOT (X-COORD Y-COORD LINELENGTH ) ((AND (< (- X-MAX) X-COORD X-MAX) (< (- Y-MAX) Y-COORD Y-MAX) ) (SET-CURSOR (- Y-MAX Y-COORD) (+ X-MAX X-COORD)) (PRIN1 DOT) ) ) $ (SETQ X-MAX (TRUNCATE (ADD1 COLUMNS) 2)) $ (SETQ Y-MAX (TRUNCATE (ADD1 ROWS) 2)) LINELENGTH is included in DOT's formal argument list to temporarily set this control variable to NIL, thus defeating muLISP's automatic line termination feature while plotting points. CONTINUE The character that is displayed when plotting a point is determined by the value of the control variable DOT. $ (SETQ DOT '*) Computers that have extended the ASCII character set may have a more appropriate character to use for plotting points. For example, (ASCII 2) is a "smiley" circle on the IBM PC. During this break you can reassign DOT, if

you so desire. BREAK CLRSCRN The function DRAW is a convenient means of clearing the screen, performing several graphics operations, and then returning the cursor to the top left corner of the screen: $ (DEFUN DRAW (NLAMBDA COMMANDS (CLEAR-SCREEN) (MAPC 'EVAL COMMANDS) (SET-CURSOR 0 0) )) During this break, test out DOT by issuing the command (DRAW (DOT 15 8) (DOT 15 -8) (DOT -15 -8) (DOT -15 8)) BREAK CLRSCRN Now that we can plot points, the next step is to implement a line-drawing routine. But first we must introduce a couple of primitively defined, numerical functions that are required by the line-drawing algorithm. The function ABS returns the absolute value of its argument: $ (ABS 24.3) $ (ABS -16) $ (ABS 0) CONTINUE The function SIGNUM returns 1 if its argument is positive, -1 if its argument is negative, and 0 if its argument is zero: $ (SIGNUM -7) $ (SIGNUM 5.3) $ (SIGNUM 0.0) CONTINUE Bresenham's algorithm is a particularly fast line-drawing algorithm because it involves only addition and subtraction. It is described in books on graphics

such as "Principles of Computer Graphics" by William M. Newman and Robert F. Sproull (McGraw-Hill Book Company, 1979). We will use it to define the function LINE that draws a line from [x1 y1] to [x2 y2]. CONTINUE If a line segment has a gradual slope (i.e. less than 45 degrees), the linedrawing routine must plot several adjacent points with the same Y-coordinate. Thus, for lines with a gentle slope, Bresenham's algorithm plots points as a function of the X-coordinate. On the other hand, if a line is steep, adjacent points are plotted as a function of the Y-coordinate. LINE calls STEEP-SLOPE or GENTLE-SLOPE depending on the steepness of the line being drawn: $ (DEFUN LINE (X1 Y1 X2 Y2 DELTA-X DELTA-Y SIGN-DELTA-X SIGN-DELTA-Y) (SETQ DELTA-X (- X2 X1) DELTA-Y (- Y2 Y1) SIGN-DELTA-X (SIGNUM DELTA-X) SIGN-DELTA-Y (SIGNUM DELTA-Y) DELTA-X (ABS DELTA-X) DELTA-Y (ABS DELTA-Y)) ((< DELTA-Y DELTA-X) (GENTLE-SLOPE) ) (STEEP-SLOPE) ) CONTINUE The gradual slope line-drawing function: $ (DEFUN GENTLE-SLOPE () (SETQ DELTA-Y (* 2 DELTA-Y) Y2 (- DELTA-Y DELTA-X) DELTA-X (- DELTA-X Y2)) (LOOP (DOT X1 Y1) ((EQ X1 X2)) ( ((PLUSP Y2) (INCQ Y1 SIGN-DELTA-Y) (DECQ Y2 DELTA-X) ) (INCQ Y2 DELTA-Y) ) (INCQ X1 SIGN-DELTA-X) ) ) Note the use of the special forms INCQ (INCrement Quote) and DECQ (DECrement Quote) in the definition of GENTLE-SLOPE. If <variable> is a symbol and <n> is a number, (INCQ variable n) adds <n> to the value of <variable>. It is equivalent to (SETQ variable (+ variable n)), but is more efficient. If INCQ

is called without a second argument, <variable> is incremented by one. DECQ is analogous to INCQ except it subtracts from its first argument. CONTINUE The steep slope line-drawing function: $ (DEFUN STEEP-SLOPE () (SETQ DELTA-X (* 2 DELTA-X) X2 (- DELTA-X DELTA-Y) DELTA-Y (- DELTA-Y X2)) (LOOP (DOT X1 Y1) ((EQ Y1 Y2)) ( ((PLUSP X2) (INCQ X1 SIGN-DELTA-X) (DECQ X2 DELTA-Y) ) (INCQ X2 DELTA-X) ) (INCQ Y1 SIGN-DELTA-Y) ) ) The line-drawing function LINE is now complete. For example, the command (DRAW (LINE -20 -5 0 10) (LINE 0 10 20 -5) (LINE 20 -5 -20 -5)) should draw a triangle on the screen. During this break, try drawing a box using LINE. BREAK CLRSCRN Rather than using an absolute coordinate system to draw figures, turtle graphics uses polar coordinates (i.e. line segments are specified by giving a distance and an angle from a starting point). To use our LINE function we must convert from polar to absolute coordinates. Thus we need to define functions for finding the sine and cosine of an angle. No matter how accurately the sine (or cosine) is computed, when multiplied by the length of a line segment and the result rounded to the nearest integer, the resulting coordinate can differ by one from what it would be if an exact sine (or cosine) were used. Using least-squares polynomials, we can compute sufficiently accurate rational approximations for the sine and cosine of an angle to insure that the error never exceeds one "pixel" (i.e. a graphics point). In fact, an error of one pixel is relatively unlikely for even the longest line segment that will fit on our screen. It is always possible to reduce sines and cosines to equivalent ones in the range 0 through 45 degrees. Hence we begin by defining sine and cosine functions restricted to that range.

CONTINUE Throughout the 0 through 45 degree range, a least-squares fitted quintic polynomial differs from sine by less than 1 part per 3000, while a leastsquares fitted quartic polynomial differs from cosine by less than 1 part per 2000. The diagonal of an 80 by 24 screen is less than 84 units, so if the maximum truncation error occurred at this particular bearing and if we move a distance equal to the entire diagonal, there would be about 84 chances out of 2000 for an error of one pixel. $ (DEFUN REDUCED-SIN (DEG) (/ (* DEG (+ 1324959969 (* (SETQ DEG (* DEG DEG)) (+ -67245 DEG)))) 75914915920) ) $ (DEFUN REDUCED-COS (DEG) (SETQ DEG (* DEG DEG)) (/ (+ 266153374 (* DEG (+ -40518 DEG))) 266153374) ) $ (REDUCED-SIN 45) $ (REDUCED-COS 45) CONTINUE Now for the somewhat tricky angle reduction functions: $ (DEFUN SIN (ANGLE) ((MINUSP ANGLE) (- (SIN (- ANGLE)))) (SETQ ANGLE (DIVIDE (REM ANGLE 360) 45)) (SIN-COS (CAR ANGLE) (CDR ANGLE)) ) $ (DEFUN COS (ANGLE) (SETQ ANGLE (DIVIDE (REM (ABS ANGLE) 360) 45)) (SIN-COS (+ 2 (CAR ANGLE)) (CDR ANGLE)) ) $ (DEFUN SIN-COS (N45DEG RESID) ((> N45DEG 3) (- (SIN-COS (- N45DEG 4) RESID)) ) ((ZEROP N45DEG) (REDUCED-SIN RESID)) ((EQL N45DEG 1) (REDUCED-COS (- 45 RESID))) ((EQL N45DEG 2) (REDUCED-COS RESID)) (REDUCED-SIN (- 45 RESID)) ) $ (SIN -390) CONTINUE

Now that we have a line-drawing routine and functions for finding the sine and cosine of an angle, we are ready to start implementing turtle graphics. The current position of the turtle on the screen is stored by the integer global variables X-POS and Y-POS. Rather than using SETQ directly to assign values to X-POS and Y-POS, you can use the SETPOS command, defined as follows: $ (DEFUN SETPOS (X Y) (SETQ X-POS X Y-POS Y) ) CONTINUE In turtle graphics, the turtle always has a heading. The heading is measured in degrees measured clockwise from a line pointing straight up on the screen. The following shows the angles associated with the four major directions: 0 | | 270 <----+----> 90 | | 180 CONTINUE The current heading of the turtle is the integer value of the global variable HEADING. The following TURN command is used to change the turtle's heading clockwise a given number of degrees relative to the current heading. To keep the heading within bounds, the heading is computed modulo 360 degrees. $ (DEFUN TURN (ANGLE) (SETQ HEADING (REM (+ HEADING ANGLE) 360)) ) During this break, define the SETHEADING command. This is similar to the TURN command except that the heading is simply set to the absolute heading given as an argument to the command. BREAK Our definition for the absolute SETHEADING command: $ (DEFUN SETHEADING (ANGLE) (SETQ HEADING (REM ANGLE 360)) ) CONTINUE

We can control whether or not the turtle's "pen" is marking on the screen as it moves. If the control variable PENDOWN is T, the turtle marks as it moves; if PENDOWN is NIL, the turtle does not mark. Although we could use SETQ to make assignments to PENDOWN, it is more convenient to have functions for this purpose. During this break define the functions PENDOWN and PENUP: BREAK Here are definitions for PENDOWN and PENUP: $ (DEFUN PENDOWN () (SETQ PENDOWN T) ) $ (DEFUN PENUP () (SETQ PENDOWN NIL) ) CONTINUE TURTLE is a convenient means of performing several successive turtle graphics commands. TURTLE first positions the turtle in the center of the screen pointing North (i.e. heading 0) and puts the pen down. DRAW is then called to switch to graphics mode and actually execute the commands. $ (DEFUN TURTLE (NLAMBDA COMMANDS (SETPOS 0 0) (SETHEADING 0) (PENDOWN) (APPLY 'DRAW COMMANDS) )) CONTINUE Finally, here is the definition for the FORWARD command: $ (DEFUN FORWARD (DISTANCE X-OLD Y-OLD ) (SETQ X-OLD X-POS) (SETQ Y-OLD Y-POS) (INCQ X-POS (ROUND (* DISTANCE (SIN HEADING)))) (INCQ Y-POS (ROUND (* DISTANCE (COS HEADING)))) ((NOT PENDOWN)) (LINE X-OLD Y-OLD X-POS Y-POS) ) During this break, draw an equilateral triangle using the TURTLE command: (TURTLE (FORWARD 10) (TURN 120) (FORWARD 20) (TURN 120) (FORWARD 20) (TURN 120)

(FORWARD 10)) BREAK CLRSCRN We have ignored the fact that "aspect-ratio" of the width to height of a character is not 1 on most sceens. For example, it is about 5/12 on the IBM PC in 80-character mode or about 5/6 on the IBM-PC in 40-character mode. For this reason, you may prefer the lower angular distortion of 40-column mode if available. (24 lines is the most severe cause of low-resolution, so half of the 80 columns is not much of a sacrifice.) Character graphics tends to be most satisfactory if you ignore the aspect ratio. (You can always look at the screen from a compensatory slant!) However, we leave it as an exercise to account for the aspect ratio in the turtle graphics routines. CONTINUE Now we can begin a library of useful figures from which to compose more complicated figures. As a simple start, it is convenient to have a command for advancing a given distance then turning a given angle: $ (DEFUN FORWARD-THEN-TURN (DISTANCE ANGLE) (FORWARD DISTANCE) (TURN ANGLE) ) CONTINUE Next, it is useful to have a function that makes a polygon, ending up at the starting point and initial heading. A theorem that the resulting total turn of a closed figure is 0 modulo 360 helps us know when to stop: $ (DEFUN POLY (SIDE ANGLE TOT-TURN) (SETQ TOT-TURN 0) (LOOP (FORWARD-THEN-TURN SIDE ANGLE) (SETQ TOT-TURN (REM (+ TOT-TURN ANGLE) 360)) ((ZEROP TOT-TURN)) ) ) During this break, experiment with POLY using various sides and angles. For example, try (TURTLE (SETPOS -5 -10) (POLY 20 144)) BREAK

CLRSCRN Here is a challenging problem: See if you can write a CORNER-POLY function which draws a polygon that recursively has a similar half-sized polygon outside each corner until the sides are reduced to one pixel. BREAK $ (DEFUN CORN-POL (SIDE ANGLE TOT-TURN) ((> SIDE 1) (SETQ TOT-TURN 0) (LOOP (FORWARD SIDE) (CORN-POL (SHIFT SIDE -2) (- ANGLE)) (TURN ANGLE) (SETQ TOT-TURN (REM (+ TOT-TURN ANGLE) 360)) ((ZEROP TOT-TURN)) ) ) ) Note the use of the function SHIFT in the definition. If <n> and <m> are integers and <m> is positive, (SHIFT n m) arithmetically shifts <n> LEFT <m> bits. If <m> is negative, SHIFT arithmetically shifts <n> RIGHT -<m> bits. SHIFT is used above to efficiently divide an integer by 2. Try this call on CORN-POL for starters: (TURTLE (SETPOS -5 -5) (CORN-POL 8 90)) BREAK CLRSCRN A spiral is another useful component. Here is a definition that shrinks by subtracting a fixed increment from the side until the side becomes less than the increment: $ (DEFUN SPIRAL (SIDE ANGLE INCR) (LOOP ((< SIDE INCR)) (FORWARD-THEN-TURN SIDE ANGLE) (DECQ SIDE INCR) ) ) During this break, try (TURTLE (SETPOS -10 -12) (SPIRAL 23 90 1)) BREAK CLRSCRN If SPIRAL is repeatedly called until the total turning reaches 0 modulo 360, then we will have a closed figure called a spirolateral. Define the function

SPIROLATERAL and experimentally determine some attractive spirolaterals. BREAK The spirolateral function: $ (DEFUN SPIROLAT (SIDE ANGLE INCR TOT-TURN) (SETQ TOT-TURN 0) (LOOP (SPIRAL SIDE ANGLE INCR) (SETQ TOT-TURN (REM (+ TOT-TURN (* ANGLE (TRUNCATE SIDE INCR))) 360)) ((ZEROP TOT-TURN)) ) ) Try this: (TURTLE (SETPOS 0 -6) (SPIROLAT 11 90 1)) BREAK CLRSCRN Up till now we have been doing very low resolution, character "graphics". If you have a computer capable of higher resolution graphics, you may want to take advantage of this capability. The graphics functions defined in this lesson work perfectly well for high resolution graphics if you make the following changes: 1. Redefine the point plotting function (DOT X-COORD Y-COORD) so it will properly interface with your graphics hardware. 2. Define the functions GRAPHICS-MODE and ALPHA-MODE to switch the screen between graphics and alpha modes. 3. If your computer is capable of color graphics, you can define a SETCOLOR command. CONTINUE The following definition for a plot function is for the IBM PC and IBM "look-alike" computers: $ (DEFUN IBM-DOT (X-COORD Y-COORD) ((AND (< -161 X-COORD 160) (< -101 Y-COORD 100) ) (REGISTER 2 (+ 160 X-COORD)) (REGISTER 3 (- 100 Y-COORD))

(REGISTER 0 *COLOR*) (INTERRUPT 16) ) ) If you are running muLISP on an IBM PC with a graphics display card (NOT a monochrome display card), issue the following command during this break: (MOVD 'IBM-DOT 'DOT) BREAK CLRSCRN The following definitions are for the IBM PC and IBM PC "look-alikes": $ (DEFUN SETCOLOR (COLOR) (SETQ *COLOR* (+ 3071 (LENGTH (MEMBER COLOR '(WHITE RED GREEN BLACK))))) ) $ (SETCOLOR WHITE) $ (DEFUN GRAPHICS-MODE () (REGISTER 0 4) (INTERRUPT 16) (MAKE-WINDOW 0 0 25 40) ) $ (DEFUN ALPHA-MODE () (REGISTER 0 3) (INTERRUPT 16) (CURSOR-LINES NIL) (MAKE-WINDOW 0 0 25 80) ) ;Sets color to white ;Sets up 320 x 200 color graphics mode

;Sets up 25 x 80 color alpha mode

CONTINUE The following definition for TURTLE is for the IBM PC and IBM "look-alikes": $ (DEFUN TURTLE (NLAMBDA COMMANDS (IF (NEQ (CADDDR (MAKE-WINDOW)) 40) (GRAPHICS-MODE) ) (MAKE-WINDOW 0 0 21 40) (SETPOS 0 0) (SETHEADING 0) (PENDOWN) (CATCH 'DRIVER (APPLY 'DRAW COMMANDS)) (MAKE-WINDOW 21 0 4 40) (SET-CURSOR 3 0) )) If you have modified DOT for high resolution graphics for your computer, try the following TURTLE command :

(TURTLE (SETPOS -30 15) (SPIROLAT 87 90 3)) BREAK $ (ALPHA-MODE) The use of recursion opens the door to really interesting designs and elegant graphics functions. The following function makes the intricate "C" curve: $ (DEFUN C-CURVE (DEPTH) ((ZEROP DEPTH) (FORWARD *LENGTH*) ) (TURN 45) (C-CURVE (SUB1 DEPTH)) (TURN -90) (C-CURVE (SUB1 DEPTH)) (TURN 45) ) $ (SETQ *LENGTH* 3) Try this pattern: (TURTLE (TURN 270) (SETPOS 60 -30) (C-CURVE 11)) BREAK $ (ALPHA-MODE) The following only slightly more complicated function draws the famous "Dragon" curve: $ (DEFUN D-CURVE (DEPTH FLAG) ((ZEROP DEPTH) (FORWARD *LENGTH*) ) (IF FLAG (TURN 45) (TURN -45)) (D-CURVE (SUB1 DEPTH) T) (IF FLAG (TURN -90) (TURN 90)) (D-CURVE (SUB1 DEPTH) NIL) (IF FLAG (TURN 45) (TURN -45)) ) $ (SETQ *LENGTH* 3) Try this pattern: (TURTLE (TURN 90) (SETPOS -60 0) (D-CURVE 11)) BREAK $ (ALPHA-MODE) We have barely scratched the surface of what can be accomplished with turtle graphics. If you would like to learn more, there are many good books on LOGO and turtle graphics. One of the more advanced and thorough is "Turtle Graphics" by Harold Abelson and Andrea A. diSessa, (MIT Press, 1980).

As a convenience to you, all the functions defined in this lesson have been included in the muLISP source file GRAPHICS.LIB. This concludes muLISP lesson #6. CONTINUE $ (RDS)

Potrebbero piacerti anche