160 likes | 182 Views
Clojure 4. Feb 17, 2015. 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 “(+ 1 2 3 4 5)”) (class (read-string “(+ 1 2 3 4 5)”))
E N D
Clojure 4 Feb 17, 2015
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 “(+ 1 2 3 4 5)”) • (class (read-string “(+ 1 2 3 4 5)”)) • (eval (read-string “(+ 1 2 3 4 5)”)) • (class (eval (read-string “(+ 1 2 3 4 5)”))) • (let [expression] (read-string “(+ 1 2 3 4 5)”)] (cons (read-string “*”) (rest expression))) • (let [expression] (quote (+ 1 2 3 4 5))] (cons (quote *) (rest expression))) • ‘(+ 1 2 3 4 5)
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 • (defmacrocond “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 1 2 3 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 ‘(4 5 6 7 8)) • `(1 2 3 ~other-numbers 9 10) • (concat ‘(1 2 3) other-numbers ‘( 9 10)) • `(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 ~@next) and#))))
More detail about and • Version that isn’t quite right. . . • (defmacro our-and [x] ([] true) ([x] x) ([x & next] `(if ~x (our-and ~@next) ~x))) • (our-and (do (println “hi there”) (= 1 2)) (= 3 4)) • Prints “hi there” twice.