Feb 17, 2015 Clojure 4
Macros Code is data We have heard this before. It is what makes Lisp so amenable to the use of macros. Examples from Mastering Clojure Macros...
Transforming code (read-string “( )”) (class (read-string “( )”)) (eval (read-string “( )”)) (class (eval (read-string “( )”))) (let [expression] (read-string “( )”)] (cons (read-string “*”) (rest expression))) (let [expression] (quote ( ))] (cons (quote *) (rest expression))) ‘( )
Our first macro (defmacro when “Evaluates test. If logical true, evaluates body.” [test & body] (list ‘if test (cons ‘do body))) (when (= 2 (+ 1 1)) (print “You got”) (print “ the touch!”) (println))
First macro continued (list ‘if ‘(= 2 (+ 1 1)) (cons ‘do ‘((print “You got”) (print “ the touch!”) (println)))) (if (= 2 (+ 1 1)) (do (print “You got”) (print “ the touch!”) (println)))
Another example (defmacro cond “Long comment here.” [& clauses] (when clauses (list ‘if (first clauses) (if (next clauses) (second clauses) (throw (IllegalArgumentException. “cond requires an even number of forms”))) (cons ‘clojure.core/cond (next (next clauses))))))
macroexpand-1 (macroexpand-1 ‘(when (= 1 2) (println “math is broken”))) (macroexpand-1 nil) (defmacro broken-when [test & body] (list test (cons ‘do body))) Use macroexpand-1 to figure out why this doesn’t work. Note that macroexpand-1 expands one level.
macroexpand (defmacro when-falsy [test & body] (list ‘when (list ‘not test) (cons ‘do body))) (macroexpand-1 ‘(when-falsy (= 1 2) (println “hi”))) (macroexpand ‘(when-falsy (= 1 2) (println “hi”)))
assert (defmacro assert [x] (when *assert* ;; make sure enabled (list ‘when-not x (list ‘throw (list ‘new AssertionError (list ‘str “Assert failed: “ (list ‘pr-str (list ‘quote x)))))))) (assert (= 1 2)) Real assert is even more complicated.
Assert with syntax quote (defmacro assert [x] (when *assert* `(when-not ~x (throw (new AssertionError (str “Assert failed: “ (pr-str ‘~x))))))) Before when-not above is a back-quote (aka a syntax quote). It selectively quotes all but things with ~ or
Syntax quote (def a 4) ‘(1 2 3 a 5) (list a 5) `(1 2 3 ~a 5) The book says syntax quote (`)is a little cockeyed and ready to party. Unquote (~) is the thing that evaluates inside of syntax quoted code/data. Unquote-splice is like unquote, but it splices in the result.
Unquote-splice (def other-numbers ‘( )) `(1 2 3 ~other-numbers 9 10) (concat ‘(1 2 3) other-numbers ‘( 9 10)) `( )
Symbol capture (def y 100) (defmacro make-adder [x] `(fn [~’y] (+ ~x ~’y))) ((make-adder (+ y 3)) 5) Fix the problem with gensym (gensym) generates a unique new symbol
Symbol capture cont. (defmacro make-adder [x] (let [y (gensym)] `(fn [~y] (+ ~x ~y)))) Auto-gensym makes this more concise. (defmacro make-adder [x] `(fn [y#] (+ ~x y#)))
Another example (defmacro and ([] true) ([x] x) ([x & next] `(let [and# ~x] (if and# (and and#))))
More detail about and Version that isn’t quite right... (defmacro our-and [x] ([] true) ([x] x) ([x & next] `(if ~x (our-and ~x))) (our-and (do (println “hi there”) (= 1 2)) (= 3 4)) Prints “hi there” twice.