Functions instead of macros

I recently had to use a function for a macro transformation. That worked, and now I wonder, why not everybody writes macros like this.

See this example.

(defn revf [expr]
  (if (list? expr) (cons (last expr)
                         (map revf (rest (reverse expr))))
      expr))

(defmacro rev [expr]
  (revf expr))

(rev (3 7 -)) ;=> 4
(macroexpand-1 '(rev (3 7 -))) ;=> (- 7 3)

I see no disadvantages. Why does Clojure not do it this way? The purpose of macros is to sit between the reader and the evaluator. But why does Clojure not uses normal functions for this? Why is there the restriction that macros do not support recursion if this can be achieved so easily?

Could I define my own version of defmacro that uses functions in this way? It would be like this:

(defmacro defrecursivemacro [name args body]
  `(do (defn ~name ~args ~body)
       (defmacro ~(symbol (str name "_macro")) ~args (~name ~@args))))

And then recursive macros work:

(defrecursivemacro reval [expr]
  (if (list? expr) (cons (last expr)
                         (map reval (rest (reverse expr))))
      expr))

The naming is not perfect. But this is a detail, that can be fixed.

why not everybody writes macros like this.

Plenty of people do. Using a macro as a wrapper around a form-manipulating function that does all the work is one of the most common recommendations that I encounter online when people ask about complicated macros.

Why does Clojure not do it this way?

The question is ambiguous.
If you mean why defmacro doesn’t actually define two vars, like your defrecursivemacro does, that it’s self-explanatory - because it defines two vars instead of one. It does more work than one might need, it has a hard-coded suffix "_macro", it hinders full-text search.
If you mean by the standard library of Clojure doesn’t use that approach - well, it doesn’t need to. Its macros work just fine they way they’re written.

1 Like

recursive macros already work without the extraneous aux function:

(defmacro and
  ([] true)
  ([x] x)
  ([x & next]
   `(let [and# ~x]
      (if and# (and ~@next) and#))))

clojure.core uses auxillary functions all over the place to help implement more complex macros that emit/process a lot of code. deftype/defrecord do (emit-deftype*, emit-defrecord). Many of the user-presented macros (like loop, let, etc) that wrap the special forms, used auxillary functions to implement destructuring and argument verification.

1 Like

FWIW macros are just functions, with some metadata and 2 extra args. But just functions otherwise. You can even create them with regular defn.

(defn a-confused-macro
  {:macro true}
  [&form &env & body]
  `(prn :yo))

(a-confused-macro foo bar)
6 Likes

Clojure does use functions to implement macros in lots of places, particularly in more complex macros. For example, the internal destructure function does all the heavy lifting to implement destructuring in let, loop, etc. All the polymorphism features like defrecord, deftype, defprotocol etc make extensive use of helper functions (these features are really a hybrid of macros, functions, and compiler impl).

1 Like

Right, I should’ve been more precise. I meant the macros that do not use any functions that are meant only to help that macro manipulate forms. Plenty of those as well.

1 Like