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))))