This article is the last part of our Clojure macros tutorial. The previous parts were:
Now we are going to show you how clojure
ninjas write macros.
The disp
macro - by 2 anonymous ninjas
The disp
macro receives expressions and returns a symbol with the expressions and their respective evaluations. (A symbol is much more pretty than a string.)
(ns my.best$macros)
(defn symbol-several
"returns a symbol with the concatenation of the str values of the args"
[& x]
(symbol (apply str x)))
(defmacro disp [& forms]
(cons
`symbol-several
(for [form forms]
`(str '~form " => " ~form "\n"))))
(my.best/disp
(map inc [1 2 3])
(+ 4 5 6))
Two smart guys on Clojurians Slack helped me to write the disp
macros.
The defprint
macro - by Herwig Hochleitner
The defprint
macro defines a function with a tweak: each time the function is called, it prints the values of its arguments. The tricky part is that it works also with desctructuring.
(ns my.best$macros)
(defmacro defprint [func-name args & body]
`(defn ~func-name [& args#]
(print '~func-name "called with: " args#)
(let [~args args#]
~@body)))
(my.best/defprint foo [a b c] (+ a b c))
(foo 1 2 3)
And here is what is printed in when foo
is called:
(with-out-str (foo 1 2 3))
Another example, with arguments desctructuring:
(my.best/defprint hello-world [& {:keys [language]
:or {language :en}}]
(case language
:fr "bonjour monde"
:en "hello world"))
(hello-world :language :fr)
(with-out-str (hello-world :language :fr))
And here is the code generated by the macro:
(macroexpand-1 '(my.best/defprint foo [& {:keys [language]} :or {language :en}]))
Take a couple of minutes to meditate on the power of this part of the macro:
(let [~args args#]
~@body)
Herwig Hochleitner helped me to write the defprint
macro.
You can read more about desctructuring.
The doseq-indexed
macro - by Tim Baldridge
doseq-indexed
works like doseq
with an additional binding to the index.
Tim Baldridge (core.async
author) wrote an elegant implementation for doseq-indexed
:
(ns my.best$macros)
(defmacro doseq-indexed [index-sym [item-sym coll] & body]
`(doseq [[~item-sym ~index-sym]
(map vector ~coll (range))]
~@body))
(with-out-str
(my.best/doseq-indexed i [x [10 100 1000]]
(println "i: " i "x: " x)))
Here is Tim’s original gist.
Feel free to share your favorite macros in the comments below.
Clojure rocks!