{:draft ["true"]}
;
; This is a macro that generates the code to print "Hello"
;
(defmacro hello []
(list 'print "Hello"))
(hello)
;
; Clojure provides a facility (macroexpand) that allows us
; to see what code is generated from a macro call.
;
(macroexpand '(hello))
;
; This shows how limiting a function is. It cannot
; examine _code_ as data.
;
(defn hello [expr]
(println "Hello: " expr))
;
; Note, the input to `hello` is the result of the code, not the code itself.
;
(hello (+ 1 2 3))
;
; But macro gets the code as data *before* it is evaluated.
;
(defmacro hello [expr]
(let [expr-as-str (str expr)]
(list println "Hello" expr-as-str)))
(macroexpand '(hello (+ 1 2 3)))
(hello (+ 1 2 3))
Suppose we want to generate
(let [x 10] (inc x))
;
; From first principle, very very impractical
;
(list 'let (vector 'x 10) (list clojure.core/inc 'x))
;
; Clojure offers the *backtick* escape form.
; *NOTE*:
; backtick: `
; singlequote: '
; We want to use the backtick form.
;
`(let [x 10] (inc x))
;
; The backtick allows us to disable the escape mode.
;
(defn get-limit[] (rand-int 100))
; Want to generate this
; (let [x __] (inc x))
;
`(let [x ~(get-limit)]
(inc x))
Note:
The tilda ~(...)
form is executed during macro expansion phase, and the result is inserted
into the quoted form.
There is another macro expansion phase form, called slicing. Suppose we want to generate the following code:
(println ____ _____ _____)
(defn get-limits []
(take 3 (repeatedly get-limit)))
(get-limits)
;
; Here the insertion by ~ is not sufficient. We don't want to insert back a list,
; but we want to insert the result as a sublist of the (println .... ) form.
;
`(println ~(get-limits))
The ~@expr
will execute expr
in macro expansion phase, and insert the result as a list
back into the form one element at a time.
`(println ~@(get-limits))
There is one more code generate feature, symbol generation.
(defmacro verbose [expr]
; Remember expr is a data fragment
`(do (println ~(str expr))
(println "=>" ~expr)))
(verbose (+ 1 2 3))
(verbose (repeat 3 (+ 1 2)))
; Don't forget the single quote the form you
; want to expand.
(clojure.pprint/pprint
(macroexpand '(verbose (repeat 3 (+ 1 2)))))
(+ 1 2)
(+ 1 2 3 4 5 67 8)
(defmacro infix [form]
(let [[a op b] form]
`(~op ~a ~b)))
(infix (1 + 2))
(macroexpand '(infix (10 * 20)))
(--- (apply + (range 1e6)))
; => (apply = (range 1e6)) took 100 ms
847298729357
(defn now [] (System/currentTimeMillis))
(now)
(defmacro ---
[expr]
`(let [start# (now)
result# ~expr
duration# (- (now) start#)]
(println ~(str expr) "took" duration# "ms")
result#))
(clojure.pprint/pprint
(macroexpand-1 '(--- (+ 1 2))))
(--- (apply + (range 1e6)))