Clojure Macros: When can a function not duplicate a macro's behaviour?

  • A+

I'm playing around with clojure macros and I'm finding that a lot of macro behavior I can just replicate with function composition.

A good example of this is the threading macro:

(defn add1 [n] (+ n 1)) (defn mult10 [n] (* n 10))  (defn threadline [arg]   (-> arg       add1       mult10)) 

I can replicate this easily with a higher order function like pipe:

(defn pipe [& fns]   (reduce (fn [f g] (fn [arg] (g(f arg)))) fns))  (def pipeline   (pipe    #(+ % 1)    #(* % 10))) 

There must be instances where a macro can not be replaced by a function. I was wondering if someone had some good examples of these sorts of situations, and the reoccurring themes involved.


One important advantage of macros is their ability to transform code at compile-time without evaluating any of it. Macros receive code as data during compilation, but functions receive values at run-time. Macros allow you to extend the compiler in a sense.

For example, Clojure's and and or are implemented as recursive macros that expand into nested if forms. This allows lazy evaluation of the and/or's inner forms i.e. if the first or form is truthy, its value will be returned and none of the others will be evaluated. If you wrote and/or as a function, all its arguments would be evaluated before they could be examined.

Short-circuiting control flow isn't an issue in your pipe function example, but pipe adds considerable run-time complexity compared to -> which simply unrolls to nested forms. A more interesting macro to try to implement as a function might be some->.

I'm finding that a lot of macro behavior I can just replicate with function composition

If your functions are amenable to it, you can certainly replace a simple threading macro with function composition with comp, similar to "point free" style in other functional languages: #(-> % inc str) is functionally equivalent to (comp str inc) and #(str (inc %)).

It's generally advised to prefer functions when possible, and even when writing a macro you can usually farm out most of the "work" to function(s).


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: