Software Architecture

Playing with Macros

For somebody with experience with Lisp of functional programming, all this will seem trivial. But I needed to refresh all this, so here  is the result of my small investigation of Clojure macro and how it relates to lazy evaluation and quoting. I also make a few test on the  difference between macro and functions.

First I started with a global variable, and a function that produces a side-effect (incrementing the value) and then return the value of the global variable. It is then trivial to see when stuff gets evaluated.

(def glob-1 10)
(defn side-effect [] (do (def glob-1 (inc glob-1)) glob-1 ))

Then to clearly see the difference between macro and function I defined these two ones:

(defmacro print-twice [exp] `(do (side-effect)(println ~exp)(println ~exp)))
(defn print-twice-2 [exp] (do (side-effect)(println exp)(println exp)))

Here is the result of the macro:

(print-twice glob-1) "11 11"
(print-twice (side-effect))  "13 14"

We clearly see how the macro expands. In the 2nd call, the  side-effect is executed 3 times as expected. We can now try the function:

(print-twice-2 glob-1) "14 14"
(print-twice-2 (side-effect)) "16 16"

We see that glob-1 is evalued before being passed to the function. As a consequence, 15 is never printed! The side-effect passed as parameter in the 2nd call is evaluated once, before the actual function is run.  After these two calls, glob-1 has a value of 17.

To have a side-effect which doesn’t hard-code the reference to glob-1, we can turn it into a macro that takes a variable as parameter.

(defmacro side-effect-1 [v] `(do (def ~v (inc ~v)) ~v ) )

The result are (of course) then similar:

(def glob-1 20)
(print-twice glob-1) "21 21"
(print-twice (side-effect-1 glob-1 ))  "23 24"
(print-twice-2 glob-1) "24 24"
(print-twice-2 (side-effect-1 glob-1 )) "26 26"

OK, that’s great, but quasi-quoting isn’t limited to macro. I can also do it in a function.

(defn print-twice-3 [exp] `(do (side-effect)(println ~exp)(println ~exp)))

The result of this function is an expression. It needs to be evaluated to produce the actual result.

(def glob-1 30)
(eval (print-twice-3 glob-1)) "30 30"
(eval (print-twice-3 (side-effect))) "32 32"

We clearly see here again that the semantic of the parameters is not the same between macro and function. The parameter glob-1 is evaluated before being passed to the function, and as a consequence ~exp expands twice to a literal value of 30, not the symbol glob-1 as in a macro, and we don’t get “31 31” as we would with the macro. The same happens for the 2nd call.


Other links related to macro and meta-programming:

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s