Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use TailCalls?

Tags:

If I understand correctly, scala.util.control.TailCalls can be used to avoid stack overflows for non-tail-recursive functions by using a trampoline. The example given in the API is straightforward:

import scala.util.control.TailCalls._

def isEven(xs: List[Int]): TailRec[Boolean] =
   if (xs.isEmpty) done(true) else tailcall(isOdd(xs.tail))

def isOdd(xs: List[Int]): TailRec[Boolean] =
   if (xs.isEmpty) done(false) else tailcall(isEven(xs.tail))

isEven((1 to 100000).toList).result 

However, the more interesting case is if you want to do some operations after the recursve call. I got a "naive" factorial implementation somehow running by

def fac(n:Long): TailRec[Long] = 
    if (n == 0) done(1) else done(n * tailcall(fac(n - 1)).result)

but this looks horrible and I doubt that this is the intended use. So my question is how to write a factorial or fibonacci function correctly using TailCalls (yes, I know how to use accumulators to get them tail-recursive)? Or is TailCalls not suitable for this kind of problem?

like image 907
Landei Avatar asked Dec 13 '10 12:12

Landei


People also ask

How does tail call optimization work?

Tail call optimization happens when the compiler transforms a call immediately followed by a ret into a single jmp . This transformation saves one instruction, and more importantly it eliminates the implicit push/pop from the stack done by call and ret .

Does Lua support tail calls?

Another interesting feature of functions in Lua is that they do proper tail calls. (Several authors use the term proper tail recursion, although the concept does not involve recursion directly.) After f calls g , it has nothing else to do.

Does JS support tail call optimization?

If the return statement of the recursive function is a call to itself, i.e return recursiveFunction() and nothing else, then the javascript engine will be able to optimize the tail call and not grow the stack.

What is tail recursion in scheme?

Scheme is “properly tail recursive”, meaning that tail calls or recursions from certain contexts do not consume stack space or other resources and can therefore be used on arbitrarily large data or for an arbitrarily long calculation.


2 Answers

Yes, your naive factorial will not be tail recursive, and will use stack space linear in the value of the argument. The purpose of scala.util.control.TailCalls is not to magically turn non-tail-recursive algorithms into tail recursive ones. It's purpose is to allow cycles of mutually tail-called functions to execute in constant stack space.

The Scala compiler implements tail-recursion optimization for methods which call themselves in tail position, allowing the stack frame of the calling method to be used by the caller. It does this essentially by converting a provably tail-recursive call to a while-loop, under the covers. However, due to JVM restrictions there's no way for it to implement tail-call optimization, which would allow any method call in tail position to reuse the caller's stack frame. This means that if you have two or more methods that call each other in tail position, no optimization will be done, and stack overflow will be risked. scala.util.control.TailCalls is a hack that allows you to work around this problem.

BTW, It's well worth looking at the source to scala.util.control.TailCalls. The "result" call is where all the interesting work gets done, and it's basically just a while loop inside.

like image 128
Dave Griffith Avatar answered Oct 25 '22 10:10

Dave Griffith


This question is more than 6 years old, but the accepted answer doesn't seem to answer the question:

So my question is how to write a factorial or fibonacci function correctly using TailCalls (yes, I know how to use accumulators to get them tail-recursive)?

So, here it is:

object Factorial {

  /**
    * Returns the nth factorial
    */
  def apply(n: Long): BigInt = {
    if (n < 0)
      throw new IllegalArgumentException("Can't factorial to an index less than 0")
    else {
      import scala.util.control.TailCalls._
      def step(current: Long): TailRec[BigInt] = {
        if (current <= 0)
          done(1)
        else
          tailcall {
            for {
              next <- step(current - 1)
            } yield current * next
          }
      }
      step(n).result
    }
  }

}

assert(Factorial(0) == 1)
assert(Factorial(7) == 5040)
assert(Factorial(10) == 3628800)

One of the big use-cases for using TailCalls is to do something that is right-fold-like.

like image 25
lloydmeta Avatar answered Oct 25 '22 09:10

lloydmeta