Lisp, part 2

Last modified: "July 3, 1996 17:10:58 by matt"

COMMENTS:

Lisp ignores anything following a semicolon on a line.

SYMBOLS and PACKAGES

packageName::symbol-name 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 "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))))