Lisp, part 4

Last modified: "September 18, 1997 11:45:53 by matt"

FANCY parameter lists to lambda expressions (like DEFUN):

&key, &optional, &rest More complex DEFUN argument lists. [See Graham 6.3 or Steele for more details.] &optional (defun power (base &optional exp) ;If arg isn't provided, it's init'd to NIL (expt base (if exp exp 2))) ;How would you rewrite the following using OR instead of IF? ;What would be the relative efficiency? (defun power (base &optional (exp 2)) ;EXP is set to 2 if caller doesn't provide a value (expt base exp)) (defun power (base &optional (exp 2 exp-provided-p)) ;Like prev example, but ;exp-provided-p is also set to T if caller provides a value. (if (null exp-provided-p) (format t "POWER: second arg defaulting to 2~%")) (expt base exp)) &key keyword arguments -- non-positional optional arguments (defun wheee (&key x y) (format t "x = ~a and y = ~a~%" x y)) (whee :x 3 :y 4) equals (whee :x 4 :y 3) As with optionals, key parms can be provided defaults: (defun wheee (&key (x 5) (y 'default-y yDefaultProvided-p)) ;3rd arg is a flag. (if (null yDefaultProvided-p) (format t "WHEEE: Y arg is defaulting.~%")) (format t "x = ~a and y = ~a~%" x y)) &rest To accept any number of args, use &rest, which jams all "remaining" args into a list. (defun arb-args (reqdArg &rest allOtherArgs) (format t "reqdArg = ~a and all the others = ~a~%" reqdArg allOtherArgs)) > (arb-args 5 1 2 3) (arb-args 5 1 2 3) reqdArg = 5 and all the others = (1 2 3) NIL > (arb-args 5 ) (arb-args 5 ) reqdArg = 5 and all the others = NIL NIL > (arb-args 5 nil ) (arb-args 5 nil ) reqdArg = 5 and all the others = (NIL) NIL > ;======================================================== Mixing &rest and &optional is weird, so be careful: (defun example (&rest x &key y) (list x y)) (example :y 3) ;============================================

CONTROLLING EVALUATION [let's see you do *this* in C or C++!] [Graham 10.1]

EVAL allows us to cross the line between lists and code.

EVAL

(eval '(+ 1 2)) (eval (+ 1 2)) Why do both work? (Why do both give the same result?) (eval '(cons 'a '(b c))) (eval (cons '+ '(1 2))) Why do both work? Why do both of the following work? (defun our-if (test true-case false-case) (cond (test (eval true-case)) (t (eval false-case)))) (defun our-if (test true-case false-case) (eval (cond (test true-case) (t false-case)))) ;;How to call these functions? (our-if (= bob 3) (print "lo") (print "hi")) ;What's wrong here? (our-if (= bob 3) '(print "lo") '(print "hi")) Context, context, context... (defun our-if-weirder (test true-case false-case) (let ((*n* 1)) ;Introduce variable (eval (cond (test true-case) (t false-case))))) (setq *n* 6) (our-if (> *n* 3) '(- *n* 3) '(+ *n* 3)) ;What gives? Why doesn't this work correctly? ;It is a limitation of EVAL, which does not ;take a lexical context. I.e., the eval'd ;expression can't refer to the vars established ;by LET. See Graham pg.161. ;======================================================

LISP in LISP

(loop (format t ">>> ") (format t "~a~%" (eval (read)))) ;====================================================== So, EVAL is limited in that it does not have a lexical context. It is also inefficient in that the argument list has to be interpretted or compiled on the spot. There can't be any precompilation. Note, too, that we couldn't really make OUR-IF behave like a normal IF, because we can't prohibit all it's arguments being evaluted unless we explicitly QUOTE them. [Recall that IF is a special operator, meaning that, unlike most functions, not all its arguments need be evaluated before the operator is invoked.] We get around these problems through the use of macros, which elegantly cross the divide between lists and code. ======================================================

MACROS [Graham Ch.10]

Macro eval is 2-stage: eval the macro to get a piece of code, then eval the code. 1st stage, the arg is NOT evald. A DEFUN defines the value a call should produce, while a DEFMACRO defines how a call should be _translated_. This translation is called macro- expansion. The effect of a macro is as if you had typed the translation directly into the program. The compiler can take advantage of this and compile the translation a priori. ;; Define cities as (state population) (setq boca '(florida 20000)) So (car boca) == 'florida (cadr boca) == 20000 (defmacro state (loc) (list 'car loc)) (defmacro pop (loc) (list 'cadr loc)) ;Evaluate the expressions below to get a feel for how macros work: (state '(florida 10000)) (pop '(florida 10000)) (setq boca '(florida 10000)) (pop boca) (state boca) (macroexpand '(state boca)) (macroexpand '(state '(florida 10000))) (defmacro my-pop (stack) (list 'let nil (list 'car stack) (list 'setq stack (list 'cdr stack)))) > (macroexpand '(my-pop aStack)) (setq aStack '( 1 2 3 4)) > (my-pop aStack) aStack But all those LISTS and QUOTES are a giant pain! The BACKQUOTE saves us lots of work: Kind of like a QUOTE, but allows controlled evaluation: '(a b c) `(a b c) (setq b '(1 2)) > `(a ,b c) (A (1 2) C) > `(a ,@b c) ;SPLICING (A 1 2 C) (defmacro my-pop (stack) `(let nil (car ,stack) (setq ,stack (cdr ,stack)))) (macroexpand '(my-pop aStack)) (defmacro my-push (e stack) `(setq ,stack (cons ,e ,stack))) There are two common errors in writing macro: variable capture and multiple evaluation. Say we wanted a macro that would repeat a chunk of code a given number of times. E.g.: > (ntimes 10 (format t ".")) .......... NIL Here's a first (buggy) attempt (defmacro ntimes (n &rest body) `(do ((x 0 (+ x 1))) ((>= x ,n)) ,@body)) Problem here is if references a variable named x. Consider: > (let ((x 10)) (ntimes 5 (setf x (1+ x))) x) This is the problem of variable capture. Below is a better solution: ;;; Remember that the point of a macro is to evaluate into a chunk of code (defmacro ntimes (n &rest body) (let ((g (gensym))) ;Gensym returns a uniquely-named symbol. `(do ((,g 0 (+ ,g 1))) ((>= ,g ,n)) ,@body))) But this still suffers from the problem of multiple evaluation. Because the first term is inserted directly into the resulting code (the ",n") it will be evald at each iteration. This can be a real problem if this code has side effects. > (let ((v 10)) (NTIMES (setf v (1- v)) (format t "."))) (defmacro ntimes (n &rest body) (let ((g (gensym)) (h (gensym))) `(let ((,h ,n)) (do ((,g 0 (+ ,g 1))) ((>= ,g ,h)) ,@body)))) A good way of debugging macros is through the use of macroexpand-1: > (pprint (macroexpand-1 '(cond (a b) (c d e) (t f))))