Search code examples
clojuremacroslisp

Clojure - how macroexpansion works inside of the "some" function


Just when I thought I had a pretty good handle on macros, I came across the source for some which looked a bit odd to me at first glance.

(defn some
  [pred coll]
    (when (seq coll)
      (or (pred (first coll)) (recur pred (next coll)))))

My first instinct was that seems like it would be stack consuming, but then I remembered: "No, dummy, or is a macro so it would simply expand into a ton of nested ifs".

However mulling it over a bit more I ended up thinking myself in a corner. At expansion time the function source would look like this:

(defn some
  [pred coll]
    (when (seq coll)
      (let [or__4469__auto__  (pred (first coll))]
         (if or__4469__auto__ 
             or__4469__auto__ 
             (recur pred (next coll))))))

Now what's got me confused is that final recur call. I've always thought that macroexpansion occurs prior to runtime, yet here you have to actually call the already expanded code at runtime in order for the second macroexp .... wait a second, I think i just figured it out.

There is no second macroexpansion, there are no nested if blocks, only the one if block. The call to recur just keeps rebinding pred and coll but the same single block above keeps testing for truth until it finds it, or the collection runs out and nil is returned.

Can someone confirm if this is a correct interpretation? I had initially confused myself thinking that there would be an interleaving of macroexpansion and runtime wherein at runtime the call to recur would somehow result in a new macro call, which didn't make sense since macroexpansion must occur prior to runtime. Now I think I see where my confusion was, there is only ever one macro expansion and the resulting code is used over and over in a loop.


Solution

  • To start with, note that any function can serve as an implicit loop expression. Also, recur works just like a recursive function call, except it does not use up the stack because of a compiler trick (that is why loop & recur are "special forms" - they don't follow the rules of normal functions).

    Also, remember that when is a macro that expands into an if expression.

    Having said all that, you did reach the correct conclusion.