Clojure - Mistakes When Writing Macros
Mistakes When Writing Macros
Variable capture
This problem occurs when a variable name from within the macro clashes from the one outside.
For example if we had a macro that binds someting to message and we call that same macro
with the variable also named message, only the message defined in the macro would be
used.
1(def message "global message")
2(defmacro example
3 [& to-execute]
4 (concat (list 'let ['message "macro message"])
5 to-execute))
6
7(example (println "The message is: " message))
8; => The message is: macro message
To combat this we can use gensym to create a unique symbol. We can also use auto-gensym
to shorten the syntax.
1(gensym)
2; => G__456
3
4(gensym 'message)
5; => message3760
6
7; auto-gensym
8`(message#)
9; => (message__4498__auto__)
Here is a fixed version of the code above
1(def message "global message")
2(defmacro fixed-example
3 [& to-execute]
4 `(let [message# "macro message"]
5 ~@to-execute
6 (println "Message from the macro is: " message#)))
7
8(fixed-example (println "The message is: " message))
9; => The message is: global message
10; => Message from the macro is: macro message
Double Evaluation
Double evaluation occurs when a form passed to a macro as an argument gets evaluated more than once. Here is an example:
1(defmacro report
2 [to-try]
3 `(if ~to-try
4 (println (quote ~to-try) "was successful: " ~to-try)
5 (println (qoute ~to-try) "was not successful: " ~to-try)))
Here the to-try would be evaluated twice: once for the if and once in the println. This is
a problem when to-try is some expensive code that takes time to run. With this macro we would
double that execution time.
To fix this we can place to-try in a let expression to make it evaluate only once.
1(defmacro report
2 [to-try]
3 `(let [result# ~to-try]
4 (if result#
5 (println (qoute ~to-try) "was successful: " result#)
6 (println (qoute ~to-try) "was not successful: " result#))))
Too Many Macros
A thing with macros is they only compose with each other so by using them we can miss out
on other kinds of composition. For example we can’t use doseq with a macro or use a macro
with map. We would need to write more macros to do what we want to. This creates more code
where it is not really needed.