2

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.

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

Comments

0

There are two modes of recursion going on here:

  • The or macro is implicitly recursive, provoked by the sequence of argument forms into generating a tree of if forms.
  • The some function is explicitly recursive, provoked into telling the single sequence of its final argument. The fact that this recursion is recurable is irrelevant.

Every argument to the or macro beyond the first generates a nested if form. For example, ...

=> (clojure.walk/macroexpand-all '(or a b c)) 
(let* [or__5501__auto__ a]
  (if or__5501__auto__ or__5501__auto__
    (let* [or__5501__auto__ b]
      (if or__5501__auto__ or__5501__auto__ c))))

You have two arguments to or, so one if form. As Alan Thompson's excellent answer points out, the surrounding when unwraps into another if form.

You can have as many nested if forms as you like, the leaves of the if tree, all of them, are in tail position. Hence all immediate recursive calls there are recurable. If there was no such tail recursion, the recur call would fail to compile.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.