9

In Scala 2.8.x, a new annotation (@tailrec) has been added that gives a compile-time error if the compiler cannot perform a tail-call optimization on the annotated method.

Is there some similar facility in Clojure with respect to loop/recur?

EDIT: After reading the first answer to my question (thanks, Bozhidar Batsov) and further searching in the Clojure docs, I came across this:

(recur exprs*)
Evaluates the exprs in order, then, in parallel, rebinds the bindings of the recursion point to the values of the exprs. If the recursion point was a fn method, then it rebinds the params. If the recursion point was a loop, then it rebinds the loop bindings. Execution then jumps back to the recursion point. The recur expression must match the arity of the recursion point exactly. In particular, if the recursion point was the top of a variadic fn method, there is no gathering of rest args - a single seq (or null) should be passed. recur in other than a tail position is an error.

Note that recur is the only non-stack-consuming looping construct in Clojure. There is no tail-call optimization and the use of self-calls for looping of unknown bounds is discouraged. recur is functional and its use in tail-position is verified by the compiler [emphasis is mine].

(def factorial
  (fn [n]
    (loop [cnt n acc 1]
       (if (zero? cnt)
            acc
          (recur (dec cnt) (* acc cnt))))))
Ralph
  • 31,584
  • 38
  • 145
  • 282

2 Answers2

6

Actually the situation in Scala w.r.t. Tail Call Optimisation is the same as in Clojure: it is possible to perform it in simple situations, such as self-recursion, but not in general situations, such as calling an arbitrary function in tail position.

This is due to the way the JVM works -- for TCO to work on the JVM, the JVM itself would have to support it, which it currently doesn't (though this might change when JDK7 is released).

See e.g. this blog entry for a discussion of TCO and trampolining in Scala. Clojure has exactly the same features to facilitate non-stack-consuming (= tail-call-optimised) recursion; this includes throwing a compile-time error when user code tries to call recur in non-tail position.

Michał Marczyk
  • 83,634
  • 13
  • 201
  • 212
  • I don't think this is correct. `recur` _instructs_ the Clojure compiler that the call is tail-recursive, whereas `@tailrec` _asks_ the Scala compiler if the call is tail-recursive. Because Scala can often not guarantee that a call is tail-recursive (e.g. for non-final methods), one should be able to get a lot more tail-recursion optimization with Clojure than with Scala, at the cost of having to be explicit. – Peter Niederwieser Mar 26 '11 at 15:30
  • Of course one could argue that Scala also has an explicit mechanism for tail recursion optimization, for example by introducing a nested method. But it isn't as obvious/idiomatic as in Clojure. – Peter Niederwieser Mar 26 '11 at 15:43
  • Hm. I thought `@tailrec` was meant to cause the compiler to error out whenever the annotated method couldn't be turned into a loop. [The relevant documentation page](http://www.scala-lang.org/api/current/scala/annotation/tailrec.html) seems to confirm this (as does a quick experiment with a `@tailrec` annotated, non-tail-recursive factorial method). Clojure's `recur` causes the same behaviour (a `recur` in non-tail position is an error and causes the compiler to complain). I'm not a Scala expert, though (not by a long shot!), so perhaps I'm missing some important subtlety here...? – Michał Marczyk Mar 29 '11 at 05:37
  • As I previously mentioned tailrec is just a compiler hint (similar to Override in Java) - it just asks the compiler to check if this is really a tail-call optimizable method. The optimization would occur with or without the annotation... – Bozhidar Batsov Sep 30 '11 at 08:50
  • That's a useful thing to know about Scala, but not all that relevant to the question at hand, which is about ensuring that a tail call is compiled into a loop in Clojure (and that the compiler errors out if that's not possible). The latter is possible if and only if the tail call is actually a self call or if the callee gets inlined in the caller -- same story as in Scala, as far as I can tell. – Michał Marczyk Sep 30 '11 at 11:15
  • 1
    I do not think I made any remark on how `scalac` works when `@tailrec` is not specified; still, I suppose to be completely fair to Scala it would have been good to state the annotation is not necessary, so thanks for your comment. – Michał Marczyk Sep 30 '11 at 11:16
4

There is no tail-call optimization when you use loop/recur AFAIK. A quote from the official docs:

In the absence of mutable local variables, looping and iteration must take a different form than in languages with built-in for or while constructs that are controlled by changing state. In functional languages looping and iteration are replaced/implemented via recursive function calls. Many such languages guarantee that function calls made in tail position do not consume stack space, and thus recursive loops utilize constant space. Since Clojure uses the Java calling conventions, it cannot, and does not, make the same tail call optimization guarantees. Instead, it provides the recur special operator, which does constant-space recursive looping by rebinding and jumping to the nearest enclosing loop or function frame. While not as general as tail-call-optimization, it allows most of the same elegant constructs, and offers the advantage of checking that calls to recur can only happen in a tail position.

Bozhidar Batsov
  • 55,802
  • 13
  • 100
  • 117
  • 4
    This answer seems misleading to me. The point of loop / recur is precisely a limited form of TCO -- namely TCO for self recursion (in a function or a loop form). Fully general TCO would also optimise stack usage for tail calls to other functions. So while Clojure does not have TCO (the general form), it does have TCO'd self-calls from recur. That's what the "constant-space recursive looping" is about. Ultimately, recur does exactly the same thing as Scala's @tailrec, which is what the question is about. (Here's hoping that this issue goes away with JDK7.) – Michał Marczyk Apr 26 '10 at 16:15
  • I guess you haven't used Scala that much - simple tail optimization there happens even without tailrec, but you'll get an error if you mark a method as tailrec, that is not actually tail-call recursive. Your comment is far more misleading than my answer, but everyone is entitled to an opinion... – Bozhidar Batsov Sep 30 '11 at 08:47
  • So your opinion is that "[t]here is no tail-call optimization when you use loop/recur", even though right after stating it you cite a passage from the docs (as you say) which states that looping via `recur` does not consume stack? Also, this question is not about the behaviour of `scalac` when `@tailrec` is not specified, it's about the possibility of obtaining in Clojure the sort of guarantees `@tailrec` gives you in Scala (perfectly possible, just use `recur`). – Michał Marczyk Sep 30 '11 at 11:06
  • I answer the remaining point about `scalac` below my answer, since you raised it there too. BTW, I am certainly under the impression that Scala is a great language and would hate to sell it short -- but to the best of my knowledge, it follows the Java-like calling convention and can therefore provide TCO in exactly the same circumstances Clojure can. (The fact that Clojure took the aesthetic decision to require a `recur`, while `@tailrec` is optional, does not change that fundamental fact.) – Michał Marczyk Sep 30 '11 at 11:23
  • If I am somehow wrong about this (w.r.t. Scala 2.8 on JDK <= 1.6, which this question is about), please do let me know. Also, I would have liked to have this discussion 17 months ago, when the question was actually asked -- I regret to say it's been rather a long time since I had any dealings with Scala now. Have a good day. – Michał Marczyk Sep 30 '11 at 12:04