LISP Tutorial 1: Basic LISP Programming

When you start up the Common LISP environment, you should see a prompt, which means that LISP is waiting for you to enter a LISP expression. In the environment I am using, it looks like the following:

USER(1):
The Common LISP environment follows the algorithm below when interacting with users:
loop read in an expression from the console; evaluate the expression; print the result of evaluation to the console; end loop.

Common LISP reads in an expression, evaluates it, and then prints out the result. For example, if you want to compute the value of (2 * cos(0) * (4 + 6)), you type in:

USER(1): (* 2 (cos 0) (+ 4 6))
Common LISP replies:

Complex arithmatic expressions can be constructed from built-in functions like the following:

Numeric FunctionsMeaning
(+ x1 x2 . xn) The sum of x1, x2, . xn
(* x1 x2 . xn) The product of x1, x2, . xn
(- x y) Subtract y from x
(/ x y) Divide x by y
(rem x y) The remainder of dividing x by y
(abs x) The absolute value of x
(max x1 x2 . xn) The maximum of x1, x2, . xn
(min x1 x2 . xn) The minimum of x1, x2, . xn

Common LISP has a rich set of pre-defined numerical functions. For a complete coverage, consult Chapter 12 of the book, Common LISP, The Language (2nd Edition) (CLTL2) by Guy Steele. In general, we will not be able to cover all aspects of Common LISP in this tutorial. Adventurous readers should consult CLTL2 frequently for more in-depth explanation of various features of the language.

Exercise: Look up pages 376-378 of CLTL2 and find out what the functions floor and ceiling are for. Then, find out the subtle difference between mod and rem.

Defining Functions

Evaluating expressions is not very interesting. We would like to build expression abstractions that could be reused in the future. For example, we could type in the following:

USER(2): (defun double (x) (* x 2)) DOUBLE

In the above, we define a function named double, which returns two times the value of its input argument x. We can then test-drive the function as below:

USER(3): (double 3) 6 USER(4): (double 7) 14

Editing, Loading and Compiling LISP Programs

Most of the functions we would like to write is going to be a lot longer than the double function. When working with complex programs, it is usually desirable to edit the program with an editor, fully debug the code, and then compile it for faster performance. Use your favorite text editor (mine is emacs) to key in the following function definition:

;;; testing.lisp ;;; by Philip Fong ;;; ;;; Introductory comments are preceded by ";;;" ;;; Function headers are preceded by ";;" ;;; Inline comments are introduced by ";" ;;; ;; ;; Triple the value of a number ;; (defun triple (X) "Compute three times X." ; Inline comments can (* 3 X)) ; be placed here. ;; ;; Negate the sign of a number ;; (defun negate (X) "Negate the value of X." ; This is a documentation string. (- X))

Save the above in the file testing.lisp. Now load the definition into the LISP environment by typing:

USER(5): (load "testing.lisp") ; Loading ./testing.lisp T
Let us try to see if they are working properly.
USER(6): (triple 2) 6 USER(7): (negate 3) -3
When functions are fully debugged, we can also compile them into binaries:
USER(8): (compile-file "testing.lisp")

Depending on whether your code is well-formed, and what system you are using, some compilation messages will be generated. The compiled code can be loaded into the LISP environment later by using the following:

USER(9): (load "testing") ; Fast loading ./testing.fasl T

Control Stuctures: Recursions and Conditionals

Now that we are equipped with all the tools for developing LISP programs, let us venture into something more interesting. Consider the definition of factorials:

n! = 1 if n = 1
n! = n * (n - 1)! if n > 1
We can implement a function to compute factorials using recursion:
(defun factorial (N) "Compute the factorial of N." (if (= N 1) 1 (* N (factorial (- N 1)))))
Relational OperatorsMeaning
(= x y) x is equal to y
(/= x y) x is not equal to y
( x y) x is greater than y
( = x y) x is no less than y

To better understand the last point, we can make use of the debugging facility trace (do not compile your code if you want to use trace):

USER(11): (trace factorial) (FACTORIAL) USER(12): (factorial 4) 0: (FACTORIAL 4) 1: (FACTORIAL 3) 2: (FACTORIAL 2) 3: (FACTORIAL 1) 3: returned 1 2: returned 2 1: returned 6 0: returned 24 24

Tracing factorial allows us to examine the recursive invocation of the function. As you can see, at most one recursive call is made from each level of invocation.

Exercise: The N'th triangular number is defined to be 1 + 2 + 3 + . + N. Alternatively, we could give a recursive definition of triangular number as follows:

T(n) = 1 if n = 1
T(n) = n + T(n-1) if n > 1

Use the recursive definition to help you implement a linearly recursive function (triangular N) that returns the N'th triangular number. Enter your function definition into a text file. Then load it into LISP. Trace the execution of (triangular 6).

Exercise: Write down a recursive definition of B E (assuming that both B and E are non-negative integers). Then implement a linearly recursive function (power B E) that computes B E . Enter your function definition into a text file. Then load it into LISP. Trace the execution of (power 2 6).

Multiple Recursions

Recall the definition of Fibonacci numbers:

Fib(n) = 1 for n = 0 or n = 1
Fib(n) = Fib(n-1) + Fib(n-2) for n > 1
This definition can be directly translated to the following LISP code:
(defun fibonacci (N) "Compute the N'th Fibonacci number." (if (or (zerop N) (= N 1)) 1 (+ (fibonacci (- N 1)) (fibonacci (- N 2)))))

Again, several observations can be made. First, the function call (zerop N) tests if N is zero. It is merely a shorthand for (= N 0). As such, zerop returns either T or NIL. We call such a boolean function a predicate, as indicated by the suffix p. Some other built-in shorthands and predicates are the following:

ShorthandMeaning
(1+ x) (+ x 1)
(1- x) (- x 1)
(zerop x)(= x 0)
(plusp x)(> x 0)
(minusp x)( Exercise: The Binomial Coefficient B(n, r) is the coefficient of the term x r in the binormial expansion of (1 + x) n . For example, B(4, 2) = 6 because (1+x) 4 = 1 + 4x + 6x 2 + 4x 3 + x 4 . The Binomial Coefficient can be computed using the Pascal Triangle formula:
B(n, r) = 1 if r = 0 or r = n
B(n, r) = B(n-1, r-1) + B(n-1, r) otherwise
Implement a doubly recursive function (binomial N R) that computes the binomial coefficient B(N, R).

Some beginners might find nested function calls like the following very difficult to understand:

(+ (fibonacci (- N 1)) (fibonacci (- N 2)))))

To make such expressions easier to write and comprehend, one could define local name bindings to represent intermediate results:

(let ((F1 (fibonacci (- N 1))) (F2 (fibonacci (- N 2)))) (+ F1 F2))

The let special form above defines two local variables, F1 and F2, which binds to Fib(N-1) and Fib(N-2) respectively. Under these local bindings, let evaluates (+ F1 F2). The fibonacci function can thus be rewritten as follows:

(defun fibonacci (N) "Compute the N'th Fibonacci number." (if (or (zerop N) (= N 1)) 1 (let ((F1 (fibonacci (- N 1))) (F2 (fibonacci (- N 2)))) (+ F1 F2))))

Notice that let creates all bindings in parallel. That is, both (fibonacci (- N 1)) and (fibonacci (- N 2)) are evaluated first, and then they are bound to F1 and F2. This means that the following LISP code will not work:

(let ((x 1) (y (* x 2))) (+ x y))

LISP will attempt to evaluate the right hand sides first before the bindings are established. So, the expression (* x 2) is evaluated before the binding of x is available. To perform sequential binding, use the let* form instead:

(let* ((x 1) (y (* x 2))) (+ x y))
LISP will bind 1 to x, then evaluate (* x 2) before the value is bound to y.

Lists

Numeric values are not the only type of data LISP supports. LISP is designed for symbolic computing. The fundamental LISP data structure for supporting symbolic manipulation are lists. In fact, LISP stands for "LISt Processing."

Lists are containers that supports sequential traversal. List is also a recursive data structure: its definition is recursive. As such, most of its traversal algorithms are recursive functions. In order to better understand a recursive abstract data type and prepare oneself to develop recursive operations on the data type, one should present the data type in terms of its constructors, selectors and recognizers.

  1. nil: Evaluating nil creates an empty list;
  2. (cons xL): Given a LISP object x and a list L, evaluating (cons xL) creates a list containing x followed by the elements in L.

Notice that the above definition is inherently recursive. For example, to construct a list containing 1 followed by 2, we could type in the expression:

USER(21): (cons 1 (cons 2 nil)) (1 2)

LISP replies by printing (1 2), which is a more readable representation of a list containing 1 followed by 2. To understand why the above works, notice that nil is a list (an empty one), and thus (cons 2 nil) is also a list (a list containing 1 followed by nothing). Applying the second constructor again, we see that (cons 1 (cons 2 nil)) is also a list (a list containing 1 followed by 2 followed by nothing).

Typing cons expressions could be tedious. If we already know all the elements in a list, we could enter our list as list literals. For example, to enter a list containing all prime numbers less than 20, we could type in the following expression:

USER(22): (quote (2 3 5 7 11 13 17 19)) (2 3 5 7 11 13 17 19)

Notice that we have quoted the list using the quote special form. This is necessary because, without the quote, LISP would interpret the expression (2 3 5 7 11 13 17 19) as a function call to a function with name "2" and arguments 3, 5, . 19 The quote is just a syntactic device that instructs LISP not to evaluate the a form in applicative order, but rather treat it as a literal. Since quoting is used frequently in LISP programs, there is a shorthand for quote:

USER(23): '(2 3 5 7 11 13 17 19) (2 3 5 7 11 13 17 19)
The quote symbol ' is nothing but a syntactic shorthand for (quote . ).

The second ingredient of an abstract data type are its selectors. Given a composite object constructed out of several components, a selector form returns one of its components. Specifically, suppose a list L1 is constructed by evaluating (cons x L2), where x is a LISP object and L2 is a list. Then, the selector forms (first L1) and (rest L1) evaluate to x and L2 respectively, as the following examples illustrate:

USER(24): (first '(2 4 8)) 2 USER(25): (rest '(2 4 8)) (4 8) USER(26): (first (rest '(2 4 8))) 4 USER(27): (rest (rest '(2 4 8))) (8) USER(28): (rest (rest (rest '(8)))) NIL

Finally, we look at recognizers, expressions that test how an object is constructed. Corresponding to each constructor of a data type is a recognizer. In the case of list, they are null for nil and consp for cons. Given a list L, (null L) returns t iff L is nil, and (consp L) returns t iff L is constructed from cons.

USER(29): (null nil) T USER(30): (null '(1 2 3)) NIL USER(31): (consp nil) NIL USER(32): (consp '(1 2 3)) T

Notice that, since lists have only two constructors, the recognizers are complementary. Therefore, we usually need only one of them. In our following discussion, we use only null.

Structural Recursion with Lists

As we have promised, understanding how the constructors, selectors and recognizers of lists work helps us to develop recursive functions that traverse a list. Let us begin with an example. The LISP built-in function list-length counts the number of elements in a list. For example,

USER(33): (list-length '(2 3 5 7 11 13 17 19)) 8

Formally, we could implement our own version of list-length as follows:

(defun recursive-list-length (L) "A recursive implementation of list-length." (if (null L) 0 (1+ (recursive-list-length (rest L)))))

Here, we use the recognizer null to differentiate how L is constructed. In case L is nil, we return 0 as its length. Otherwise, L is a cons, and we return 1 plus the length of (rest L). Recall that (1+ n) is simply a shorthand for (+ n 1).

Again, it is instructive to use the trace facilities to examine the unfolding of recursive invocations:

USER(40): (trace recursive-list-length) (RECURSIVE-LIST-LENGTH) USER(41): (recursive-list-length '(2 3 5 7 11 13 17 19)) 0: (RECURSIVE-LIST-LENGTH (2 3 5 7 11 13 17 19)) 1: (RECURSIVE-LIST-LENGTH (3 5 7 11 13 17 19)) 2: (RECURSIVE-LIST-LENGTH (5 7 11 13 17 19)) 3: (RECURSIVE-LIST-LENGTH (7 11 13 17 19)) 4: (RECURSIVE-LIST-LENGTH (11 13 17 19)) 5: (RECURSIVE-LIST-LENGTH (13 17 19)) 6: (RECURSIVE-LIST-LENGTH (17 19)) 7: (RECURSIVE-LIST-LENGTH (19)) 8: (RECURSIVE-LIST-LENGTH NIL) 8: returned 0 7: returned 1 6: returned 2 5: returned 3 4: returned 4 3: returned 5 2: returned 6 1: returned 7 0: returned 8 8
  1. Use the recognizers to determine how X is created (i.e. which constructor creates it). In our example, we use null to decide if a list is created by nil or cons.
  2. For instances that are atomic (i.e. those created by constructors with no components), return a trivial value. For example, in the case when a list is nil, we return zero as its length.
  3. If the instance is composite, then use the selectors to extract its components. In our example, we use first and rest to extract the two components of a nonempty list.
  4. Following that, we apply recursion on one or more components of X. For instance, we recusively invoked recursive-list-length on (rest L).
  5. Finally, we use either the constructors or some other functions to combine the result of the recursive calls, yielding the value of the function. In the case of recursive-list-length, we return one plus the result of the recursive call.

Exercise: Implement a linearly recursive function (sum L) which computes the sum of all numbers in a list L. Compare your solution with the standard pattern of structural recursion.

Sometimes, long traces like the one for list-length may be difficult to read on a terminal screen. Common LISP allows you to capture screen I/O into a file so that you can, for example, produce a hard copy for more comfortable reading. To capture the trace of executing (recursive-list-length '(2 3 5 7 11 13 17 19)), we use the dribble command:

USER(42): (dribble "output.txt") dribbling to file "output.txt" NIL USER(43): (recursive-list-length '(2 3 5 7 11 13 17 19)) 0: (RECURSIVE-LIST-LENGTH (2 3 5 7 11 13 17 19)) 1: (RECURSIVE-LIST-LENGTH (3 5 7 11 13 17 19)) 2: (RECURSIVE-LIST-LENGTH (5 7 11 13 17 19)) 3: (RECURSIVE-LIST-LENGTH (7 11 13 17 19)) 4: (RECURSIVE-LIST-LENGTH (11 13 17 19)) 5: (RECURSIVE-LIST-LENGTH (13 17 19)) 6: (RECURSIVE-LIST-LENGTH (17 19)) 7: (RECURSIVE-LIST-LENGTH (19)) 8: (RECURSIVE-LIST-LENGTH NIL) 8: returned 0 7: returned 1 6: returned 2 5: returned 3 4: returned 4 3: returned 5 2: returned 6 1: returned 7 0: returned 8 8 USER(44): (dribble)

The form (dribble "output.txt") instructs Common LISP to begin capturing all terminal I/O into a file called output.txt. The trailing (dribble) form instructs Common LISP to stop I/O capturing, and closes the file output.txt. If we examine output.txt, we will see the following:

dribbling to file "output.txt" NIL USER(43): (recursive-list-length '(2 3 5 7 11 13 17 19)) 0: (RECURSIVE-LIST-LENGTH (2 3 5 7 11 13 17 19)) 1: (RECURSIVE-LIST-LENGTH (3 5 7 11 13 17 19)) 2: (RECURSIVE-LIST-LENGTH (5 7 11 13 17 19)) 3: (RECURSIVE-LIST-LENGTH (7 11 13 17 19)) 4: (RECURSIVE-LIST-LENGTH (11 13 17 19)) 5: (RECURSIVE-LIST-LENGTH (13 17 19)) 6: (RECURSIVE-LIST-LENGTH (17 19)) 7: (RECURSIVE-LIST-LENGTH (19)) 8: (RECURSIVE-LIST-LENGTH NIL) 8: returned 0 7: returned 1 6: returned 2 5: returned 3 4: returned 4 3: returned 5 2: returned 6 1: returned 7 0: returned 8 8 USER(44): (dribble)

Symbols

The lists we have seen so far are lists of numbers. Another data type of LISP is symbols. A symbol is simply a sequence of characters:

USER(45): 'a ; LISP is case-insensitive. A USER(46): 'A ; 'a and 'A evaluate to the same symbol. A USER(47): 'apple2 ; Both alphanumeric characters . APPLE2 USER(48): 'an-apple ; . and symbolic characters are allowed. AN-APPLE USER(49): t ; Our familiar t is also a symbol. T USER(50): 't ; In addition, quoting is redundant for t. T USER(51): nil ; Our familiar nil is also a symbol. NIL USER(52): 'nil ; Again, it is self-evaluating. NIL

With symbols, we can build more interesting lists:

USER(53): '(how are you today ?) ; A list of symbols. (HOW ARE YOU TODAY ?) USER(54): '(1 + 2 * x) ; A list of symbols and numbers. (1 + 2 * X) USER(55): '(pair (2 3)) ; A list containing 'pair and '(2 3). (pair (2 3))
Notice that the list (pair (2 3)) has length 2:
USER(56): (recursive-list-length '(pair (2 3))) 2
Notice also the result of applying accessors:
USER(57): (first '(pair (2 3))) PAIR USER(58): (rest '(pair (2 3))) ((2 3))

Lists containing other lists as members are difficult to understand for beginners. Make sure you understand the above example.

Example: nth

LISP defines a function (nth N L) that returns the N'th member of list L (assuming that the elements are numbered from zero onwards):

USER(59): (nth 0 '(a b c d)) A USER(60): (nth 2 '(a b c d)) C