Search code examples
recursionclojuremacros

clojure recursive macro definition


Hi, I am learning Clojure macros, and I am trying to write a macro that translate infix forms into prefix forms like this: (9 + (1 * 3)) => (+ 9 (* 1 3))

(defn infix [form]
  (list (second form) (first form) (nth form 2)))

(defmacro r-infix [form]
  (if (coll? form)
    (map r-infix (infix form))
    form))

(r-infix (9 + (1 * 2)));;=>ArityException

But If define the macro in the following way, it works out fine:

(defn infix [form]
  (list (second form) (first form) (nth form 2)))

(defn r-infix-fn [form]
  (if (coll? form)
    (map r-infix-fn (infix form))
    form))

(defmacro r-infix [form]
  (r-infix-fn form))

(r-infix (9 + (1 * 2)));;=>11

I have some difficulty debugging the first example, can any one help me out?


Solution

  • Macro's in Clojure are not "first class citizens", meaning they can't be used in all the ways that data and functions (which are not macros) can.

    You can't map a macro :-(

    So the first example attempts to pass the macro to a function, which results in an error about not being able to take the value of a macro. To explore why this is let's have an imaginary conversation with a sufficiently advanced compiler.

    Me: hello compiler, please take this macro and use it to transform all these expressions.

    Sufficiently-Advanced-Compiler: I tried to look at your macro, and it tried to evaluate when I was reading it, I'm not sure what you want me to pass to this function, the macro goes away when i read it???

    Me: Oh, sorry i just wanted something that will take a list and change it around.

    Sufficiently-Advanced-Compiler: That sounds like a function :-)

    macro's are functions that are marked with a special flag that causes them to run when they are read, and be totally finished before the final code is ready to run. this makes passing them to other things that aren't themselves macros very confusing when it comes to writing a compiler.

    The effect of this is a common anti-pattern called macro-contagion where code gets written as a macro only because it needs to call another macro in some dynamic way. then as a result more code get's written as a macro, and it's a downward spiral.

    Your second method shows the correct way to do this where all the work of the macro is done in normal functions (that happen to be called by a macro) that are wrapped in a thin macro layer as an entry point. When later someone needs to come along and apply r-inflix-fn to something else, a tree perhaps, then they won't have to make their new code a macro just to call yours.

    In general it's wise to be suspicious of any code that is only accessible through a macro.