Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
872 views
in Technique[技术] by (71.8m points)

scala - How to use TailCalls?

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...