As we noticed above, the primary tool for building "programs" in LISP
is to define recursive functions. There has been nothing that resembles
the sequences of statements, for-loops, and goto`s of other languages.
There are times when writing things as sequences of statements is more convenient,
than writing them as recursive functions.
There is a feature in LISP that allows us to do this.
You may be surprised that it hasn`t been mentioned before. The reason for this
is that the real power of LISP lies in its ability to handle recursive functions.
To emphasize this point, you should know, although you may not believe, that
anything that can be computed using the PROG feature that we are about to
describe can also be computed without it using only recursive functions.
It just happens that the PROG feature is, in some cases, more convenient.
Anyway, PROG is a special form and it looks like this:
(PROG (V1 V2...) S1 S2...)
Each Vi is an atomic symbol and becomes a local variable in the PROG.
Each Si is any LISP expression.
The rule for evaluating a PROG is this: First all the Vi`s are initialized
as local variables with the value NIL. Then the non-atomic Si`s are evaluated in
sequence. The value of each Si is discarded, so each one must
be a pseudo-function that somehow affects the state of affairs.
The Si`s are evaluated in sequence until you either fall off the end, in
which case the value of the entire PROG-expression is NIL, or until a transfer
of control is specified by evaluating an expression of the form (GO x).
xis a label and a label is specified by writing an atomic symbol as
an Si.
The expression (RETURN exp) also terminates execution of a PROG and the value
of the PROG expression is the value of exp written after the RETURN.
The special form SETQ is used within a PROG to change the value
of a variable. The effect of the expression (SETQ v exp) is to take
the value of the expression exp and assign it to the variable v, replacing
the old value.
SETQ can be used on either the local variables of a PROG or on the fluid
variables appearing after a LAMBDA that surrounds a PROG.
SETQ acts just like an assignment statement and is very similar to CSETQ.
The difference between the two is that CSETQ is used for global constants
while SETQ is used on local variables.
As an example of the use of PROG, let's rewrite the LENGTH function that we defined
above using the PROG feature.
(CSETQ LENGTH (LAMBDA (L)
(PROG (N)
(SETQ N 0)
LOOP
(COND ((NULL L) (RETURN N)))
(SETQ N (PLUS N 1))
(SETQ L (CDR L)))
(GO LOOP)
)))
Notice that the conditional expression (really statement here) used
above has an undefined value whenever L is not empty, but we don`t care
about that since the value is going to be discarded anyway, what we are interested
in is the side effect of the statement.
Also notice that GO`s, SETQ`s or RETURN`s can be written either as one of the
"statements" of a PROG, or they can be written as one of the branches in
a conditional in a PROG.
As another example of the use of PROG, lets write a function to reverse a
list and all its sublists so that (REVERSE `(A (B C D) E F)) =
(F E (D C B) A).
(CSETQ SUPERREVERSE (LAMBDA (L)
(PROG (SRL)
LOOP
(COND ((NULL L) (RETURN SRL))
((ATOM (CAR L)) (SETQ SRL (CONS (CAR L) SRL)))
(T (SETQ SRL (CONS (SUPERREVERSE (CAR L)) SRL))))
(SETQ L (CDR L))
(GO LOOP)
)))
Note that there are still no restrictions about functions calling
themselves recursively. Even though the SUPERREVERSE function was
defined as a PROG, it still uses itself to reverse any sublists that it finds.
Compare this with the definition of SUPERREVERSE written below as a recursive
function that requires an auxiliary function.
(CSETQ SUPERREVERSE (LAMBDA (L)
(COND
((NULL L) NIL)
((ATOM L) L))
(T (STICKATENDOF (SUPERREVERSE (CDR L))(SUPERREVERSE (CAR L))))
)))
(CSETQ STICKATENDOF (LAMBDA (L E)
(COND
((NULL L) (LIST E))
(T (CONS (CAR L) (STICKATENDOF (CDR L) E)))
)))
In this case, defining SUPERREVERSE as a PROG was slightly more convenient that
defining it as a recursive function. It also turns out that the PROG
definition is in this case more efficient in its usage of both
time and space, but don`t assume that this is always the case.
I know that once you`ve learned about the PROG feature, you will find
it so similar to FORTRAN or ALGOL or whatever language that you are already
familiar with that there will be a great temptation to write LISP programs
by writing the corresponding FORTRAN or ALGOL program and then translating
it using the PROG feature. I strongly urge you to resist this temptation.
It will certainly work but you will end up doing a lot of things
the hard way.
It is also the case that by using the PROG feature you are likely to make
a lot more mistakes. This is because the PROG feature requires to
you to apply pseudo-functions for their side effects, and side-effects by their
very nature effect other things elsewhere that you always seem to forget about.
6.2 INPUT - OUTPUT
As we saw above, the normal method of "inputting data" to a LISP program
is to write it as arguments to a function and the "output" of the program
is the result computed by that function. For most LISP applications, this
serves quite nicely; however, there are times when we want to read LISP
objects off of cards and print our answers ourselves.
The pseudo-functions below are some of the basic input-output facilities
in 1100 LISP; the Reference Manual contains a complete list.
(READ)
The value of the expression (READ) is the next LISP object that
appears in the input stream. The effect is to advance over this object so that successive READ`s read
successive LISP objects.
(READCH)
The value of the expression (READCH) is an atomic symbol that is
the next character in the input stream. The effect is to advance over this character.
(CLEARBUFF)
The effect of the expression (CLEARBUFF) is to skip to the beginning
of the next image; the value is NIL.
(SETCOL c)
The effect is to set the input routine to begin the next read in column
c of the current image. The value is NIL.
(PRINT x)
The effect is to edit the object x into LISP`s internal print buffer
and print it. The value is x.
(PRIN1 x)
The effect is to edit the object x into the internal print buffer, but
the buffer is not printed unless it overflows during editing.
The next PRIN1 or PRINT will begin in the next column of the internal buffer. The value is x.
(PRIN1 x c)
The effect is to edit the object x into the internal print buffer
beginning in column c. The value is x.
(TERPRI)
Prints the internal print buffer. The value is NIL.
(CURRCOL)
The value of the expression (CURRCOL) is the next column of the
internal print buffer that is due to receive characters.
(SPACE n)
The effect is to cause n blank lines to precede the next line of output.
The value is NIL.
As an example of the use of these Input-output pseudo-functions,
the definition below creates a LISP supervisor that goes through
essentially the same read-eval-print loop that the real LISP supervisor
does.
(CSETQ LISP (LAMBDA()
(PROG (EXP VAL)
LOOP
(CLEARBUFF)
(PRINT 'EVAL!:)
(SETQ EXP (READ))
(SETQ VAL (EVAL EXP))
(PRIN1 'VALUE!:! )
(PRINT VAL)
(GO LOOP)
)))
6.3 FUNCTIONS AS ARGUMENTS
One of the things that it is much easier to do in LISP than in other
languages is to supply functions as arguments to other functions.
The reason for this is that functions in LISP are data objects just
like anything else. We noticed this above when we merely assigned a function
to a variable in order to give it a name.
As an example, suppose that we wanted to define a function that would take
a list and another function as arguments and whose result would be a new
list consisting of the result of applying that function to each element.
E.g, assuming that we have defined a function named FACTORIAL
then we want to define a function named INTO such that the value of
(INTO `(1 2 3 4 5) FACTORIAL) is (1 2 6 24 120).
We define this function exactly like we would expect to; the only thing that
is so surprising is that the definition is so natural and easy.
(CSETQ INTO (LAMBDA(L FN)
(COND
((NULL L) NIL)
(T (CONS (FN (CAR L)) (INTO (CDR L) FN)))
)))
Note that we now could also write something like
(INTO `(1 2 3 4 5) (LAMBDA (X)(PLUS X 3))) and its value would be the list
(4 5 6 7 8).
As another example, we could define a function to find out if each
element of a list has a certain property like so:
(CSETQ IFALL (LAMBDA (L P)
(COND
((NULL L) T)
((P (CAR L)) (IFALL (CDR L) P))
(T NIL)
)))
So (IFALL `(AB4 CX XX) ATOM) is true while
(IFALL `(AB4 (C) X) ATOM) would be false.
6.4 OTHER SYSTEM DEFINED FUNCTIONS
Along with the functions and special-forms that we have learned already,
there are a few others that you might find useful. These are listed below.
A complete list of all functions available in 1100 LISP appears
in the reference manual.
In some cases, the functions below will be defined in LISP by means of
the primitive functions you know already just to give you more
examples of LISP definitions.
(NOT x)is true if and only if x is false. (NOT x) is equivalent to
(NULL x)
(ADD1 x) = x + 1.
(SUB1 x) = x - 1.
(NUMBERP x)is true if x is an integer or a real number and
false otherwise.
(MEMBER x m)is true if and only if x is one of the elements of the list m.
EQUAL is used for the comparison, not EQ.
(APPEND m1 m2)is the list consisting of the elements of m1 followed by the elements of m2. E.g, (APPEND `(A B C) `(E F G H)) = (A B C E F G H). Note the difference between APPEND and CONS.
(CSETQ APPEND (LAMBDA (L1 L2)
(COND
((NULL L1) L2)
(T (CONS (CAR L1) (APPEND (CDR L1) L2)))
)))
(SUBST x y z) = a copy of z with every subpart that is the same as y (via EQUAL)
changed to x.
E.g, (SUBST `(A) `B `(C B (B) D)) = (C (A)((A))D).
(LENGTH m) = the number of elements in the list m.
(LENGTH NIL) = 0; (LENGTH `(A B (C D)E)) = 4.
(REVERSE m) = the list m with its top-level elements written backwards.
(REVERSE `(A B (C D) E)) = (E (C D) B A).
(MAP m fn) The pseudo-function fn is applied to each element of
the list m. The value is NIL.
(CSETQ MAP (LAMBDA (L FN)
(PROG()
AGAIN
(COND ((NULL L)(RETURN NIL))
(FN (CAR L))
(SETQ L (CDR L))
(GO AGAIN)
)))
(INTO m fn) = a list each of whose elements is the result of applying
fn to an element of the list m.
See definition above in 6.3.
6.5 ONE MORE EXAMPLE
Here is a final example of a LISP session from a terminal.
In this example we use LISP to develop a system to do algebraic
simplification. The problem of algebraic simplification is quite
complex and the example here is far from anything resembling a full
treatment, but it does give an example of the kinds of things that LISP
is good at.
This example only handles a part of the simplification that we would
want to do with addition and multiplication. It assumes that the expressions
are written in LISP. We also include a pseudo-function to define "constants".
In this case, we use the editor to create a symbolic element and @ADD it into
LISP. This is so that we can save and modify the LISP program that we
are developing. This gets kind of messy since it means that we have to leave
LISP, use the editor to make changes, and come back to LISP whenever we
need to redefine a function and have the definition remembered. Or we have
to remember to change our symbolic element later and redefine the function
within LISP.
There are facilities in LISP that allow us to do text editing and filing of
function definitions, but they require a familiarity with LISP that we felt
was beyond the scope of this primer. They are detailed in the Reference
Manual.
@ASG,A MYFILE
@EDIT,I MYFILE.SIMPLIFY
EDIT 1.33-1/16-2:33
INPUT
:LOAD
<CSETQ SIMPLIFY (LAMBDA [EXP]
(COND
[(ATOM EXP) (VALUEOF EXP VALUELIST)]
[T (REDUCE (CAR EXP) (INTO (CDR EXP) SIMPLIFY))]
))
<CSETQ VALUEOF (LAMBDA [VAR VL]
(COND
[(NULL VL) VAR]
[(EQ (CAAR VL) VAR)(CADAR VL)]
[T (VALUEOF VAR (CDR VL))]
))>
<CSETQ REDUCE (LAMBDA [OP ARGS]
(COND
[(EQ OP 'PLUS) (DOPLUS (CAR ARGS) (CADR ARGS))]
[(EQ OP'TIMES) (DOTIMES (CAR ARGS) (CADR ARGS))]
[T (CONS OP ARGS)]
))>
<CSETQ DOPLUS (LAMBDA [A1 A2]
(COND
[(NUMBERP A2)
(COND
[(ZEROP A2) A1]
[(NUMBERP A1) (PLUS A1 A2)]
[(ATOM A1) (LIST 'PLUS A1 A2)]
[(AND (EQ (CAR A1) 'PLUS)(NUMBERP (CADDR A1)))
(DOPLUS (CADR A1)(PLUS (CADDR A1) A2))]
[T (LIST 'PLUS A1 A2)]
)]
[(NUMBERP A1) (DOPLUS A2 A1)]
[(EQUAL A1 A2) (DOTIMES A1 2)]
[T (LIST 'PLUS A1 A2)]
))>
<CSETQ DOTIMES (LAMBDA [A1 A2]
(COND
[(NUMBERP A2)
(COND
[(ZEROP A2) 0]
[(EQUAL A2 1) A1]
[(NUMBERP A1)(TIMES A1 A2)]
[(ATOM A1) (LIST 'TIMES A1 A2)]
[(AND (EQ (CAR A1)'TIMES) (NUMBERP (CADDR A1)))
(DOTIMES (CADR A1) (TIMES (CADDR A1) A2))]
[T (LIST 'TIMES A1 A2)]
)]
[(NUMBERP A1) (DOTIMES A2 A1)]
[T (LIST 'TIMES A1 A2)]
))>
<CSETQ VALUELIST NIL>
<CSETQ DEFINE (LAMBDA [VAR VAL]
(PROG[]
<UNDEFINE VAR>
<CSETQ VALUELIST (CONS (LIST VAR VAL) VALUELIST)>
(RETURN (LIST VAR 'DEFINED 'AS VAL))
))>
<CSETQ UNDEFINE (LAMBDA[VAR]
(PROG[L]
<SETQ L VALUELIST>
<CSETQ VALUELIST NIL>
LOOP
<COND[(NULL L) (RETURN (LIST VAR 'UNDEFINED))]
[(NOT (EQ (CAAR L) VAR))<CSETQ VALUELIST (CONS (CAR L) VALUELIST)>]>
<SETQ L (CDR L)>
<GO LOOP>
))>
:END
'SIMPLIFY! LOADED
@LISP
LINES FILED: 67
1110 LISP 7.72
EVAL:
@ADD MYFILE.SIMPLIFYVAR
VALUE: SIMPLIFY LOADED
EVAL:
(SIMPLIFY '(PLUS 0 A>
VALUE: A
EVAL:
(SIMPLIFY '(TIMES 3 (PLUS (TIMES B 1)B>
VALUE: (TIMES B 6)
EVAL:
(DEFINE 'K 6)
VALUE: (K DEFINED AS 6)
EVAL:
(SIMPLIFY '(PLUS 3 (PLUS A K>
VALUE: (PLUS A 9)
EVAL:
(SIMPLIFY '(PLUS A (DIFFERENCE K A>
VALUE: (PLUS A (DIFFERENCE 6 A))
EVAL:
(DEFINE 'C 7)
VALUE: (C DEFINED AS 7)
EVAL:
(SIMPLIFY '(TIMES C (TIMES A K>
VALUE: (TIMES A 42)
EVAL:
(UNDEFINE 'K>
VALUE: (K UNDEFINED)
EVAL:
(SIMPLIFY '(TIMES C (TIMES A K>
VALUE: (TIMES (TIMES A K) 7)
EVAL:
(SIMPLIFY '(PLUS A (PLUS C (PLUS A -7>
VALUE: (TIMES A 2)
6.6 RESERVED ATOMIC SYMBOLS
The following is a list of atomic symbols that have constant values when LISP
is loaded.
These atomic symbols, and any others that you use as names of functions,
cannot be used as names for variables. It is not necessary that you
memorize this list. Just look it over and see if there are any that you would
be likely to use (like LIST) and remember not to use those.
The ones that are underlined are the ones that have been described in this
primer; the others are described in the Reference Manual.