3
votes

The clojure macro is a difficult point for me, here is an macro example took from "Pratical Clojure":

(defmacro triple-do [form] 
  (list 'do form form form))

user=> (triple-do (println "test"))

test

test

test

nil

This triple-do works well And I think the following version should work but not

(defmacro triple-do [form] 
  (do form form form))

user=> (triple-do (println "test"))

test

nil

why does it just print once?

and the following confuses me extremely

(defmacro test-macro [form] (do form (println "hard code test")))


user=> (test-macro (println "hello"))

hard code test

nil

why "hello" not show in console?

3

3 Answers

5
votes

A macro is a function which returns a Clojure s-expression which is then compiled, or expanded again if the macro returns a macro. This process of replacement repeats recursivly until no macro's remain, then the final code is evaluated. It helps to think carefully about what runs while the macros are expanding compared to what the finally produced code will run.

The macroexpand-1 function can be really helpful by showing you what a macro expands:

user> (macroexpand-1 '(test-macro (println "hello")))
hard code test 
nil

from this you can see that the print statement is happening while the macro is expanding. If you add a syntax quote before your do, the macro may make more sense.

user> (defmacro test-macro [form] `(do ~form (println "hard code test")))
#'user/test-macro
user> (macroexpand-1 '(test-macro (println "hello")))
(do (println "hello") (clojure.core/println "hard code test"))

In this case the print runs after the macro finishes expanding.

3
votes

It will help to look at your examples through the lens of macroexpand, an essential tool for debugging macros.

Example 1:

(defmacro triple-do [form] 
  (list 'do form form form))

user=> (triple-do (println "test"))

Remember macros are supposed to return a list that will be executed at runtime. Let's see what this macro returns:

(macroexpand '(triple-do (println "test")))
;; (do (println "test") (println "test") (println "test"))

So it doesn't execute the code but rather returns a list that represents the code that will be executed once the macro is expanded. This is similar to trying the following snippet at the REPL:

(+ 1 2 3)
;; 6

(list '+ 1 2 3)
;; (+ 1 2 3)

With this in mind, let's turn to Example 2:

(defmacro triple-do [form] 
  (do form form form))

user=> (triple-do (println "test"))

Notice how the macro now doesn't return a list. It simply executes the do form which returns the last statement which is the form passed in. This can be easily seen by expanding the macro:

(macroexpand '(triple-do (println "test")))
;; (println "test")

That's why you end up with a single print statement.

This should give you a clue about Example 3:

(defmacro test-macro [form] (do form (println "hard code test")))


user=> (test-macro (println "hello"))

It's a bit trickier but let's expand it nevertheless:

(macroexpand '(test-macro (println "hello")))
;; hard code test <= this gets print before the macro fully expands
;; nil <= the expansion yields nil

Again, since you're not returning a list but rather simply executing a do form, it simply runs the println call from within the macro and since println returns nil, that becomes the result of the expansion.

To illustrate my point, this is how you'd have to rewrite your macro in order to achieve the desired behaviour:

(defmacro test-macro [form] (list 'do form (println "hard code test")))
(test-macro (println "hello"))
;; hard code test
;; hello

I hope this clears things for you.

Just remember this: macros are supposed to return lists that represent the code you'd like to be executed at runtime.

2
votes

A macro must return, at compile time, the list that will take its place in the code.

Since do takes any number of arguments and returns the last, in each of your cases the macro expands to the last form in the do block.

The original returned a list with do as the first element, so rather than returning the last element in the block, it expanded to the whole block.