How to use TailCalls?

非 Y 不嫁゛ 提交于 2019-11-28 20:30:06

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!