0
votes

This question is related to the interaction between two macros, one contained in the other. The two macros described are examples illustrating the interaction and not as the only case.

Consider a simple clojure macro which produces a side effect but otherwise passes its input unchanged.

(defmacro echo [action & args]
  `(fn [tru#] (do (~action ~@args tru#) tru#)))

Usage

((echo log/info "result") :foo)

Include that macro in another macro, e.g. the ->> macro. My intuition says to do it like the following:

(->> :foo (echo log/info "result") clojure.pprint/pprint)

That does not work as the ->> macro goes first and inserts :foo which leaves the echo macro with the wrong arguments. One way to correct the problem is to introduce additional parenthesis but that seems ugly to me:

(->> :foo ((echo log/info "result")) clojure.pprint/pprint)

By ugly I mean it smells like c macros.

1

1 Answers

4
votes

There's nothing really surprising going on here, but I think echo expanding to a function is making it harder to understand. macroexpand can help illustrate what's going on:

(macroexpand '(->> :foo (echo prn "result") clojure.pprint/pprint))
=> (clojure.pprint/pprint (echo prn "result" :foo))

Here you can see ->> is correctly threading the argument through to the second/last position of the echo form, which is not what you want because echo expands to a function that must be invoked with :foo as its only argument. If we expand your other example:

(macroexpand '(->> :foo
                   ((echo prn "result"))
                   clojure.pprint/pprint))
=> (clojure.pprint/pprint ((echo prn "result") :foo))

You can see after threading, your echo call is formed as ((echo prn "result") :foo) just like your working example above. There's no odd interaction between macros here, just confusing syntax because echo expands to a function that must be invoked.

I think this would be a lot clearer/cleaner (from a macro-expansion perspective) if echo expanded to code instead of an anonymous function. However I think you could achieve the same effect without a macro, which is usually preferable:

(defn echo [f x & args]
  (do (apply f x args)
      (last args))

(->> :foo
     (echo prn "result")
     (clojure.pprint/pprint))
;; "result" :foo
;; :foo
;; => nil

Although there are other ways to "spy" on values in threading macros. Try this for example:

(-> :foo
    (doto (prn "result"))
    (clojure.pprint/pprint))
;; :foo "result"
;; :foo
;; => nil