Meet the Macro a quick introduction to Lisp macros Patrick Stein / TC Lisp Users Group / 2009-07-14
Attack Plan Some other things called macros First look at Lisp macros More advanced macros Uh-oh, my macro is messing up other things Toy surprise in the box Where to learn more 2
Called Macro (C/LaTeX/make) #define abs(x) \ ((x) < 0? -(x) : (x)) cpp macros can do string substitutions \frac{dx}{ \sqrt[3]{1 + x^2} } LaTeX macros can be LaTeX wrappers around other LaTeX SRCS := $(wildcard *.c) GNU make macros are a mini-language 3
Called Macro (ant,nroff,m4) Ant macros can do string and element substitutions nroff macros can barely stringsubstitute m4 can cheesily subst varargs <macrodef name= say-do > <attribute name= say default= (silent) /> <element name= do /> <sequential> <echo>@{say} :::</echo> <do /> </sequential> </macrodef>.nr LL 5i define( `quotify, ` $@ ) 4
First Lisp Macros (replacement) #define assert_good_x() \ assert( x!= NULL ) (defmacro assert_good_x () '(assert x)) (defun car+cdr (x) (assert_good_x) (cons x x)) (defun car+cdr (x) (assert x) (cons x x)) A very weak cpp macro An equally weak Lisp version How you d use it How it will look expanded. 5
First Lisp Macros (arg subst.) Bad cpp macro for reference Equivalent (i.e. equally bad) version in Lisp How you d use it #define abs(x) \ (((x) < 0)? -(x) : (x)) (defmacro abs (x) `(if (minusp,x) (-,x),x)) (defun taxi-dist (x y z) (+ (abs x) (abs y) (abs z))) My taxis go vertically! (abs (realpart (ack 3 z))) 6
First Lisp Macros (lists) (defmacro awhen (cond &body body) `(let ((it,cond)) (when it,@body))) (defun a-then-b? (a b list) (awhen (member a list) (write it) (member b (rest it)))) A macro that plops in the members instead of the list itself How you might use it 7
More Advanced (lisp @ comp.) A macro that uses more Lisp at compile time How you d use it What it would be expanded to (defun absify (arg) `(abs,arg)) (defmacro taxi-dist (&rest args) `(+,@(mapcar #'absify args))) (taxi-dist a b c) (+ (abs a) (abs b) (abs c)) 8
More Advanced (recursive) ;; recursive version of same macro (defmacro taxi-dist (&rest args) (case (length args) (0 '0) (1 `(abs,(first args))) (t `(+ (abs,(first args)) (taxi-dist,@(rest args)))))) ;; some examples of how it expands (taxi-dist) => 0 (taxi-dist a) => (abs a) (taxi-dist a b) => (+ (abs a) (taxi-dist b)) (taxi-dist a b c) => (+ (abs a) (taxi-dist b c)) 9
More Advanced (code-walking) CL-WHO macro that walks through the expression tree It turns some parts into output code (with-html-output (*s*) (:body (dotimes (j 5) (htm (:p Foo ) (:p Bar ))))) (write-string "<body>" *s*) (dotimes (j 5) (write-string "<p>foo</p>" *s*) (write-string "<p>bar</p>" *s*)) (write-string "</body>" *s*) Only approximate expansion 10
More Advanced (more walking) Turn infix notation into prefix notation: (!! (let ((a 42)) a - 2 * 2 * 10)) http://folk.uio.no/jornv/infpre/infpre.html Make translators that turn Lisp into other langs http://www.cliki.net/parenscript Make embedded domain-specific languages see any number of HTML, SQL, and XML input or output DSLs 11
Uh-Oh (double eval) (defmacro abs (x) `(if (minusp,x) (-,x),x)) (abs (incf x)) (abs (realpart (ack 3 z))) Problem: if x is an expression, it s evaluated twice Side-effects x2 Runtime x2 #define abs(x) \ ((x) < 0? -(x) : (x)) abs(++x); abs(realpart(ack(3,z))); cpp version has the same issue 12
Uh-Oh (variable capture) In Lisp, we can do better We have to take (defmacro abs (x) `(let ((v,x)) (if (minusp v) (- v) v))) care though This macro breaks if body uses a v from outside this scope. (defmacro when-car=cdr (x &body body) `(let ((v,x)) (when (= (car v) (cdr v)),@body))) 13
Uh-Oh (stopping variable capture) (defmacro when-car=cdr (x &body body) (let ((v (gensym))) `(let ((,v,x)) (when (= (car,v) (cdr,v)),@body)))) (when-car=cdr p (write p)) (let ((#:G772 p)) (when (= (car #:G772) (cdr #:G772)) (write p))) Gensym bails us out by generating a symbol that won t exist anywhere else Here is a use Here is one expansion 14
Uh-Oh (macros for macro-writing) Up to your ears in gensyms? Here s a macro for macro writers with-gensyms makes syms for a and b, then we bind them to this macro s args (defun symify (s) `(,s (gensym))) (defmacro with-gensyms (syms &body body) `(let,(mapcar #'symify syms),@body)) (defmacro crow-dist (x y) (with-gensyms (a b) `(let ((,a,x)(,b,y)) (sqrt (+ (*,a,a) (*,b,b)))))) 15
Toy Surprise (destructuring) (defmacro det+? ((x1 y1) (x2 y2) &body body) `(when (plusp (- (*,x1,y2) (*,x2,y1))),@body)) (det+? (a b) (c d) (do-something a b c d)) You can put extra structure into the way your arguments are laid out to make your syntax more natural for the problem 16
Toy Surprise (deeper destruct.) ;; macro used internally in ironclad (defmacro with-words (((&rest word-vars) array initial-offset &key (size 4) (big-endian t)) &body body)...) (with-words ((c d a b) ciphertxt ciphertxt-start :big-endian nil) (setf c (logxor c (aref round-keys 4)) d (logxor d (aref round-keys 5)) a (logxor a (aref round-keys 6)) b (logxor b (aref round-keys 7)))...) 17
Where to Learn More Slava Akmechet s Blog Post About Macros http://www.defmacro.org/ramblings/lisp.html Chapters 7 & 8 of Practical Common Lisp by Peter Seibel http://gigamonkeys.com/book Chapters 7-11 of On Lisp by Paul Graham http://paulgraham.com/onlisp.html Sections 3.1.2.1.2.2 and 3.4.4 of the HyperSpec Practice, Practice, Practice 18
(sb-ext:quit) the end Patrick Stein / TC Lisp Users Group / 2009-07-14
Uh-Oh Redux (infinite recursion) This would terminate as a function, but not as a macro This will terminate (defmacro argc (&rest args) `(if,(null args) 0 (1+ (argc,@(rest args))))) (defmacro argc (&rest args) (if args `(1+ (argc,@(rest args))) '0)) X
Uh-Oh Redux (other messes) (defmacro save (x) `(push vars,x)) (defun foo (vars) (save *random-state*)...) (defmacro both+? (a b) `(and (plusp,b) (plusp,a)) (both+? (incf x) (incf y)) (defmacro emph (x) (nconc x (list ':!!))) (defun foo () (emph (list :b))) Reference global that is later local Non-standard eval order Side-effects considered harmful X
Appendix (Ackermann function) ;; positive-integer version of Ackermann ;; function... can be extended to cover ;; n being a complex number if m is 1, 2, or 3. (defun ack (m n) (cond ((zerop m) (1+ n)) ((zerop n) (ack (1- m) 1)) (t (ack (1- m) (ack m (1- n)))))) ;; (ack 3 12) took me 11.007 seconds ;; (ack 3 13) blew out my stack after 50 seconds X