Lisp, part 1

Last modified: "October 5, 1999 12:36:48 by evett"

Chapter 2 of Graham's ANSI Common Lisp

TERMS:

toplevel (listener) operator and arguments: (+ 1 2) expressions: atoms or lists. 2, (+ 2 3)

EVALUATION

read - eval - print evaluation:
  1. eval args (each an expression) left to right
  2. pass resulting vals of args to operator and evaluate
  3. "return" value of op

Note that linefeeds are ignored by lisp. They serve only to make the code more readable.

Almost all lisp ops are functions (i.e., return a value)

The QUOTE operator (abbreviated with the apostrophe "'") suspends evaluation of the next expression. In other words, QUOTE is a function that returns its argument directly, i.e., without first evaluating it. As noted above, most functions are invoked only after first evaluating their arguments. QUOTE is a special function, meaning that it is invoked without first evaluating its argument. There are several special functions in Lisp. symbols lisp programs are, themselves, lists!

LIST OPERATORS

cons, car,cdr,nth,first,listp,consp, list

All lists can be composed via a sequence of cons cells. Example: (A (B C) D) is (cons a (cons (cons 'b (cons 'c nil)) (cons 'd nil)))

Reading Lisp -- indentation and parentheses. Should be handled by your editor. Commercial lisp systems provide such editors. Emacs is one of the best, and is free. See the notes on the class Web pages.

COMMENTS:

Lisp ignores anything following a semicolon on a line. (Unless the semicolon is within a string.) This is analogous to the way C++ uses "//" to start in-line comments.

(print "comment with a semicolon ; is fine") ; A comment

VARIABLES

Any symbol can be a variable, simply assign it a value. E.g:
(setq mynewvar 5)

would define a variable called MYNEWVAR, if there wasn't one already. This can be a bit confusing if you mistype the name of a variable. Lisp compilers generate warnings in this situation.

Generally, it is poor form to introduce variables this way. Instead, use DEFVAR to define global variables, and LET to define variables local to individual functions:

(DEFVAR mynewvar 5) ; My new variable is defined here

ASSIGNMENT

setq, setf

SETQ is normally used for setting the values of variables.

SETF is used in conjunction with accessors, to set l-values, much like pointers are used in C++. For example,

(setf (car x) 'a)
is used to set the CAR of x to a. So if X is (1 2 3), then that setf would change X to be (A 2 3).

Pointers are ubiquitous and implicit in Lisp, unlike most other programming languages, such as C. This leads to real flexibility in using the language. On the other hand, it also creates the need for garbage collection, because the user is not responsible for dynamic memory management. (I.e., no MALLOC or FREE in Lisp.)

FUNCTIONAL PROGRAMMING --

No globals. All data is stored in variables local to each function. Information is passed between functions only via pass-by-value parameters, and the return values of functions. Easy to experiment with, because there are no side effects. Therefore, if a function/procedure works correctly in one instance, it should work correctly in all. Lisp is mostly a functional language, but it has many operators that do have side-effects (e.g. setq).

FUNCTIONS

Use DEFUN to define new functions. The format is:
(defun name-of-function (arg1 arg2 ...) s-exp1
s-exp2... )
where arg1 arg2 ... are the formal parameters to the new function. The value returned by a function is the value of the last s-expression of the function, or that returned by a RETURN form.

FUNCTIONS as OBJECTS

lambda expressions = a way of generating unnamed functions.
(setf (symbol-function 'sum) 
      (lambda (x y) (print "two argument summation") (+ x y)))

TYPES --

hah! No need for those in lisp, though their use does increase the efficiency of "compiled" lisp code.

PREDICATES

A predicate is a function that returns a boolean.
(atom 'a)
(atom '( 1 2 3))
(listp '(1 2 3))
(atom nil) = (listp nil) = (null nil)

CONSP == LISTP except for NIL

(equal '(a b c) (list 'a 'b 'c))

There are other equality predicates, but EQUAL is most general. True if two things print the same. EQ is "pointer equality". (EQ A B) iff A and B point to the same object.

zerop, oddp, <, <=, !=

(typep 6 'number)
(member 'b '(a b c))  ;; Doesn't just return T, since any non-NIL connotes true, and this is more informative.
(member 'b '(a (b) c))

CONDITIONALS

if   []

(cond ((listp x) (car x)))
COND is a function, returning val of 1st term evaling to non NIL. Similar to C's CASE statement.
(defun car-atomp (x)
  (cond ((listp x) (atom (car x))
	 (t nil)))		; If this is omitted, still returns NIL (by def of COND) but this
				; is more clear-cut.						
(cond ((= x 5) (setq z 3))
      (t (setq z 4)))
is completely equivalent to
(if (= x 5) (setq z 3) (setq z 4))

Parentheses are not analogous to the C++ language's '{' and '}':

If you want more than one statement in the then or else clause of an IF, use a LET form to combine them, much like C++ uses '{' and '}'. I.e., don't do this:
(if (= x 5)
  (print "X is")
  (print 5))
which is analagous to the C++ IF statement:
if (x == 5)
  cout << "X is";
  cout << 5;
Instead use
(if (= x 5)
  (let ()
     (print "X is")
     (print 5)))
which is analogous to the C++ code:
if (x == 5)
  {
     cout << "X is";
     cout << 5; }
We talk more about LET later.

LOGICAL OPERATORS

AND & OR as flow-of-control.

Evals left to right, stopping ASAP.
(or (= x 4) (not (numberp x)) (setq x 5))
is functionally equivalent to:
(if (and (not (= x 4)) (numberp x))
    (setq x 5)
    t)

(and (setq x 4) (not (numberp x)) (setq x 5))

LISP IS LEXICALLY SCOPED:

Understanding symbols vs. parameters/variables Symbols are statically bound. I.e., bound at definition, not at reference.
> (defun wow (x) (setq x 5) (bow) (print x))
WOW
> (defun bow () (setq x (* 2 x)))  ;; x is a FREE variable, here, at definition time.
BOW
> (wow 2)
>>Error: The symbol X has no global value

SYMBOL-VALUE:
   Required arg 0 (S): X

> (setq x 4)  ; defines x at top level (i.e., globally)
4
> (wow 2)

5 
5
> x
8
> (wow 2)

5 
5
> x
16
> 

Lisp uses lexical (aka static) scoping.

Dynamic scoping is possible, but what would be some problems with dynamic scoping? Debugging would be very difficult; simple examination of the program text would be insufficient to locate bugs. The program would have to be debugged during execution so that the programmer could examine the program stack. The Original Lisp used dynamic scoping.

**DANGER, special variables ahead....***

>  (defun wow (x) (declare (special x))  (setq x 5) (bow) (print x))

;; Because WOW's X is "special", references to symbol x within the time that WOW
;; is executing will refer to the x defined in WOW's scope.  In effect, WOW's x
;; "intercepts" references to "global" variable x.

> (wow 2)
10
10
;; Notice that the value of the "toplevel" x has not changed....
> x
16
>
Here is another example of dynamic scoping. We are writing a function, FIND-BIGGEST, that takes a list of positive integers, and returns a dotted pair consisting of the biggest and second-biggest integers in the list. For our first attempt, we'll use a global variable (boo, hiss!). FIND-BIGGEST uses REDUCE in conjunction with another function I wrote, BIGGESTYET and a global variable.

   (defvar second-biggest -1)  ; This global is used in BIGGESTYET

   ;;; FIND-BIGGEST (L) Returns a dotted pair: the largest integer in L (a list
   ;;; of positive integers) and he second largest integer in L.

   (defun find-biggest (L)
     (setq second-biggest -1)   ; Takes care of multiple runs of FIND-BIGGEST
     (let ((result (reduce #'biggestYet L)))
       (cons result second-biggest)))

   (defun biggestYet (a b)
     (let ((max (if (< a b) b a))
           (min (if (>< a b) a b)))
       (if (> min second-biggest)
         (setq second-biggest min))
       max))

   USER(20): (find-biggest '(1 3 8 5 2 6 2))
   (8 . 6)
Now, we will use dynamic scoping (a SPECIAL variable) to solve the same problem without a global variable. In effect, secondB is like a "temporary" global variable, that exists only within the lifetime of FIND-BIGGEST.
(defun find-biggest (L)
 (let ((secondB -1))
  (declare (special secondB))
  (let ((result (reduce #'biggestYet L)))
    (cons result secondB))))

(defun biggestYet (a b)
  (let ((max (if (< a b) b a))
	(min (if (< a b) a b)))
    (if (> min secondB)
      (setq secondB min))
    max))