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
or are implemented as recursive macros that expand into nested
if forms. This allows lazy evaluation of the
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
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
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).