The phrase "tail call optimization" often confuses because it sounds like proponents primarily want things to be "faster" - after all speed is the most common target of optimizations. But while faster is certainly nicer, what we really want from TCO is for things to not explode in the face of large or even infinite sequences of tail calls. It's a space optimization rather than a time optimization. In the right circumstances is optimizes O(n) memory stack usage to O(1). That's often far more important than a mere constant factor loss in time performance.
Hickey made an interesting choice. He makes the user do the work to indicate when trampolining should be used. That means the user gets to make the choice about the trade-offs involved.
A variation in Scala
object Sometime {
def tailCall[X](x : => Sometime[X]) = new Later({x})
implicit def xToSometime[X](x : => X) = new Now({x})
def trampoline[X](x : Sometime[X]) = x.eval
}
sealed abstract class Sometime[X] {
def eval : X = {
def eval[X](x : Sometime[X]) : X = x match {
case x : Now[X] => x.get
case x : Later[X] => eval(x.get)
}
eval(this)
}
}
class Now[X](x : => X) extends Sometime[X] {
protected def get = x
}
class Later[X](x : => Sometime[X]) extends Sometime[X] {
protected def get = x
}
import Sometime._
def foo(n : Long) : Sometime[Unit] = {
if ( n > 0)
tailCall(bar(n - 1));
}
def bar(n : Long) : Sometime[Unit] = {
if ( n > 0)
tailCall(foo(n - 1));
}
trampoline(foo(1000)), or, equivalently foo(1000).eval
On Wed, Nov 26, 2008 at 8:48 AM, Carsten Saager
<csaager@...> wrote:
Impressive:
user=> (time (trampoline #(foo 1000)) )
"Elapsed time: 0.547556 msecs"
user=> (time (foo 1000))
"Elapsed time: 0.375466 msecs"
Looks like the penalty is not that high, but after the JIT kicked in the trampolined version takes twice as long as the recusive version - hard to call this a TCO when the tail-call takes actually longer. Probably not a solution for a more performance focussed language like Scala: