Lisp, part 2

Last modified: "September 22, 1999 17:27:14 by evett"

SYMBOLS and PACKAGES

Packages are analogous to namespaces in C++. The global variable *current-package* is the name of the current package. Any variables or functions you define while be defined within that package. The use of different packages allows packages to define variables and functions with names that might be used in other packages, without having to worry about name "collisions".

Every symbol has a full name, of the form:

packageName::symbol-name

Shorter references are of the forms:

packageName:symbol-name (syntax works if package has been imported)

symbol-name (only works if *current-package* is that of symbol.)

CHARACTERS:

#\A -- is a capital A

NUMBERS:

(/ 1 2) is not what you think.... try (/ 1.0 2) instead. Or you can coerce: (float (/ 1 2))

MORE ON PREDICATES:

null: returns T if its argument is EQ NIL.

"atom", not "atomp". Inconsistent naming conventions is one of my pet peeves about Lisp.

Here's a predicate that tests to see if the car of the arg is an atom:

  (defun car-atomp (x) (atom (car x)))
A problem: what if x has no car?

MORE ON LISTS

cons can be used to add an element to a list. How do we "add" and list to another? I.e., how do we concatenate?
>(append '(a b) '(c d))
(A B C D)

LOCAL VARIABLES

LET allows the creation of local variables. LET creates a new lexical context. As a side-effect, it also allows the creation of multi-element expressions.
(defun 3-max (a b c)		;Returns whichever of three given args is largest
  (let ((ab (max a b))		;Defs a local variable, ab, set equal to max of a & b
	bc)			;Defines a local variable, and sets it to NIL, 
				;  because no initial value is given.
    ;; ab and bc are defined within the LET statement.
    (setq bc (max b c))		
    (max ab bc)))		;Because this is the last form in the LET, it will be 
				;the value of the LET.  Because the LET is the last form
				; of 3-MAX, 3-MAX's value will assume LET's.
Of course a far simpler implementation of 3-max is:
(defun 3-max (a b c)
  (max a b c))

RECURSION

Lisp, because it is functional, lends itself to recursion much more than does C or other procedural languages. Still, though recursion is easy to program, it is not always the most efficient solution.
(defun recursive-length (list)
  (if (null list)
      0
    (1+ (recursive-length (cdr list)))))

(defun factorial (n)			;Recursive bindings protect us.
  (if (zerop n) 1
    (* n (factorial (1- n)))))
(MEMBER elt list) returns the largest tail of LIST whose CAR = ELT. If no match, returns NIL.
(member 'a '(b c a d f))
MEMBER also takes keyword arguments. Keyword args are position independent (except that they must follow standard arguments), using special tokens, called keywords, to distinguish one from another.
? (member 3 '(5 4 3 2 1) :test #'>)     ; Use > instead of eql for "equality" test.
(2 1)
? (member 3 '(5 4 3 2 1) :test #'> :key #'1+) ; Apply 1+ to elts before "equality" test
(1)
? 
(defun recursive-member (elem list)	;Not quite like MEMBER, why?
  (cond ((null list) nil)		;Classic terminator for recursion on lists.
    (or (equal elem (first list))	;[Use of OR as a conditional]
	(recursive-member elem (rest list)))))  
Doesn't return tail.

Now is a good time to see how your Lisp's trace facility works: trace recursive-member.

(defun recursive-member (elem list)	;More like MEMBER
  (cond ((null list)			;Terminator for failure.
	 nil)
    ((equal elem (first list))		;Terminator for success.
     list)				;Return's matching tail.
    (t
     (recursive-member elem (rest list)))))	
The relationship between recursion and induction. In designing a recursive function, simply make sure that it works for the base case (0, NIL, e.g.). Then, assuming it works for the N case, show that it works for the N+1 case. Think recursively with lists. They are basically tree stuctures, so recursion is usually a good bet....
(defun our-subst (new old list)
  (cond
   ((equal old list) 
    new)
   ((atom list) 
    list)				;No more subst's
   (t 
    (cons (our-subst new old (car list))	;Subst in CAR
	  (our-subst new old (cdr list))))))	;Subst in CDR
In this example, "list" is actually acting as a tree. The algorithm is doubly-recursive: the third conditional clause recurses down both the CAR and the CDR of the given arg.
> (our-subst 'a 'b '(b c d (f b a) (g (h b i) j)))
(A C D (F A A) (G (H A I) J))
To see the recursion in action, we can use the trace facility, or we can add a few print statements...
(defun our-subst (new old list &optional (depth 0))
  (format t "~VTIncoming, LIST = ~a~%" depth list) 
   ;; [~5T would tab to column five.  'V', used in place of a prefix argument to
   ;; a FORMAT directive causes the argument to be taken from the arg list to
   ;; the FORMAT.  So ~VT causes a tab to the column identified by the next
   ;; argument in FORMAT, DEPTH.]

  (let ((result					;Save the return value for later printing
	 (cond					
	  ((equal old list) new)		
	  ((atom list) list)			;No more subst's
	  (t (cons (our-subst new old (car list) (1+ depth)) ;Subst in CAR
		   (our-subst new old (cdr list) (1+ depth))))))) ;Subst in CDR
    (format t "~VTReturning: ~a~%" depth result)
    result))					;Return value of the function

A more complex example, Fig 3.6 from Graham:

> (compress '(a a a b c c d e e))
((3 A) B (2 C) D (2 E))

(defun compress (x)                     ; Need a wrapper around the recursive fnct
  (if (consp x) 
      (compr (car x) 1 (cdr x))
      x))

(defun compr 
       (elt                             ; Element of list now being compressed
        n                               ; How many have already been seen/compressed
        lst)                            ; Remaining (uncompressed) list.
  (if (null lst)
      (list (n-elts elt n))
      (let ((next (car lst)))
        (if (eql next elt)
            (compr elt (+ n 1) (cdr lst))
            (cons (n-elts elt n)
                  (compr next 1 (cdr lst)))))))

(defun n-elts (elt n)                   
  (if (> n 1)
      (list n elt)
      elt))

Recursion is not always right, though:
(defun fib (n)
  (cond ((or (equal n 1) (equal n 2)) 1)
	(t (+ (fib (1- n)) (fib (- n 2))))))
It needlessly computes each FIB term an exponential number of times. For example, (fib 98) is computed for (fib 99) and (fib 100). Think about how many times (fib 2) is calculated during calculation of (fib 100). A better solution would be to 'remember' previous invocations of FIB, and to recall those previously calculated values. We'll see how to do this when we cover closures.

Anyway, this example shows that sometimes iteration is better....

ITERATION (5.4 of Graham)

do, do*, dotimes, dolist DO is syntactically very ugly, but is very powerful.
(defun calc-length (x)                  ; Return number of elts of given list (x).
  (do ((tmp x) 
       (length 0))
      ((atom tmp) length)
    (setq tmp (cdr tmp))
    (setq length (1+ length))))
A more concise version. Note that the loop now has no actual body! All the work is done in the stepping operations:
(defun calc-length (x)                  ; Return number of elts of given list (x).
  (do ((tmp x (cdr tmp))                ; Use of CDR is classic technique for itering through a list.
       (length 0 (1+ length)))
      ((atom tmp) length)))             ; NIL is an atom and a CONS.

? (calc-length '(a b c))
3
? (atom nil)
T
? (calc-length 'a)
0
? (calc-length '())
0
(defun my-reverse (x)
  (do ((tmp x (cdr tmp))
       (reversed nil (cons (first tmp) reversed)))
      ((null tmp) reversed)))
 

(defun print-list (x)
  (do ((tmp x (cdr tmp)))
      ((null tmp) 'bye-bye)
    (print (car tmp))))

(defun print-list (x)                   ; For simple iter across list elems, DOLIST is nice shortcut
  (dolist (cur x) (print cur)))

(defun print-list (x)
  (do ((tmp x (cdr tmp))			;Is anything wrong with this?
	(cur (car tmp) (car tmp)))		;Yes, because first TMP here is free var. ref.	
      ((null tmp) 'bye-bye)		
    (print cur)))

(defun print-list (x)
  (do* ((tmp x (cdr tmp))
	(cur (car tmp) (car tmp)))		;Is anything wrong with this?
      ((null tmp) 'bye-bye)			;NO, because (CAR NIL) = NIL
    (print cur)))
The RETURN function is used to abort looping constructs primarily. Unlike C or PASCAL, functions in LISP only rarely use an explicit RETURN. That is because all functions in LISP return values. Recall the the value of a function is the value of the last form executed within that function.
(defun print-list-to-stop (x)
  (do ((tmp x (cdr tmp)))
      ((null tmp) '***completed***)
    (if (equal (car tmp) 'stop)
	(return '***aborted***))
    (print (car tmp))))