Macros Aren’t Values

The first and most important drawback with macros is that we can’t treat them as values. As principled functional programmers, we’re quite used to thinking about functions as values. For example, we can pass functions as arguments to higher-order functions like map and filter to decouple looping logic from transformation and selection logic. When we use a macro instead of a function, we lose that ability:

beware/not_values_1.clj
 
user=> (​defn​ square [x] (​*​ x x))
 
;=> #'user/square
 
user=> (​map​ square (​range​ 10))
 
;=> (0 1 4 9 16 25 36 49 64 81)
 
user=> (​defmacro​ square [x] `(​*​ ~x ~x))
 
;=> #'user/square
 
user=> (​map​ square (​range​ 10))
 
;CompilerException java.lang.RuntimeException:
 
; Can't take value of a macro: #'user/square, compiling: (NO_SOURCE_PATH:1:1)

This might not seem like a big deal when you just consider the definitions of the two squaring verbs we’ve defined here. But imagine you’re not the author of square, just a user—in a functional language, it can be really annoying to run into this limitation. Oh, and if you’ve spotted another problem with the macro version of square, congrats! If not, don’t worry—we’ll take a closer look in Macros Can Be Tricky to Get Right.

In this specific case, you can work around the issue by wrapping the macro in a function call:

beware/not_values_2.clj
 
user=> (​defmacro​ square [x] `(​*​ ~x ~x))
 
;=> #'user/square
 
user=> (​map​ (​fn​ [n] (square n)) (​range​ 10))
 
;=> (0 1 4 9 16 25 36 49 64 81)

This works because when the anonymous function (fn [n] (square n)) gets compiled, the square expression gets macroexpanded, to (fn [n] (clojure.core/* n n)). And this is a perfectly reasonable function, so we don’t have any problems with the compiler. This can be a decent workaround whenever you’re able to put the name of the macro in the verb position. Of course, many macros do more complicated things that make this function-wrapping technique impossible. Even a simple macro can prevent you from doing this if it does anything interesting with its input expressions:

beware/not_values_3.clj
 
(​defmacro​ do-multiplication [expression]
 
(​cons​ `​*​ (​rest​ expression)))
 
 
user=> (do-multiplication (​+​ 3 4))
 
;=> 12
 
user=> (​map​ (​fn​ [x] (do-multiplication x)) ['(​+​ 3 4) '(​-​ 2 3)])
 
; CompilerException java.lang.IllegalArgumentException:
 
; Don't know how to create ISeq from: clojure.lang.Symbol,
 
; compiling:(NO_SOURCE_PATH:1:14)

You might have noticed that this macro is pretty silly—it just ignores the verb in its input and replaces it with multiplication. It assumes that its input expression is something that we can use to create a sequence. So when Clojure tries to macroexpand (do-multiplication x), it can’t do its job, because x is a symbol. And as the error message tells us, Clojure doesn’t know how to turn a symbol into a sequence. At the risk of belaboring the point, macros take code as input–they don’t (and can’t) know what values will be substituted in place of the symbols in the code at runtime.

The only way to map across expressions to get what we want here is to convert do-multiplication to a function that outputs an expression, and then either write a little interpreter or use the dreaded eval to compute the results. Neither of these is anything resembling a macro-as-a-value. So while we can cheat and wrap some extremely simple macros in functions, in the general case we’re left entirely without a way to treat them as values.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.140.197.10