Scala 如何使尾部递归方法也可以以非尾部递归的方式引用自身
假设我有一个长时间运行的计算机制,可以暂停它们自己,以便稍后恢复:Scala 如何使尾部递归方法也可以以非尾部递归的方式引用自身,scala,recursion,tail-recursion,Scala,Recursion,Tail Recursion,假设我有一个长时间运行的计算机制,可以暂停它们自己,以便稍后恢复: sealed trait LongRunning[+R]; case class Result[+R](result: R) extends LongRunning[R]; case class Suspend[+R](cont: () => LongRunning[R]) extends LongRunning[R]; 运行它们的最简单方法是 @annotation.tailrec def repeat[R](body
sealed trait LongRunning[+R];
case class Result[+R](result: R) extends LongRunning[R];
case class Suspend[+R](cont: () => LongRunning[R]) extends LongRunning[R];
运行它们的最简单方法是
@annotation.tailrec
def repeat[R](body: LongRunning[R]): R =
body match {
case Result(r) => r
case Suspend(c) => {
// perhaps do some other processing here
println("Continuing suspended computation");
repeat(c());
}
}
问题是产生这样的计算。假设我们要实现每10个周期暂停计算的尾部递归阶乘:
@annotation.tailrec
def factorial(n: Int, acc: BigInt): LongRunning[BigInt] = {
if (n <= 1)
Result(acc);
else if (n % 10 == 0)
Suspend(() => factorial(n - 1, acc * n))
else
factorial(n - 1, acc * n)
}
如何在非挂起调用上保留尾部递归?我找到了一个可能的答案。我们可以将尾部递归部分移动到内部函数中,并在需要时参考外部函数非尾部递归:
def factorial(n: Int, acc: BigInt): LongRunning[BigInt] = {
@annotation.tailrec
def f(n: Int, acc: BigInt): LongRunning[BigInt] =
if (n <= 1)
Result(acc);
else if (n % 10 == 0)
Suspend(() => factorial(n - 1, acc * n))
else
f(n - 1, acc * n)
f(n, acc)
}
def阶乘(n:Int,acc:BigInt):长时间运行[BigInt]={
@注释.tailrec
def(n:Int,acc:BigInt):长时间运行[BigInt]=
if(n阶乘(n-1,acc*n))
其他的
f(n-1,附件*n)
f(n,acc)
}
我说的对吗?这将在每个阶乘调用上重新创建闭包实例?@om nom nom我不确定Scala对此类闭包的优化程度如何。不管怎么说,它们只是为停赛而设计的,停赛并不经常发生。(这个例子有点简化。)但我可以从我的观察中说,内存占用保持不变。我测试了另一个类似的计算,使用了1000000个悬浮液。它运行得非常快,只消耗了很少的内存。“我说的对吗,这将在每个阶乘调用上重新创建闭包实例”:不,闭包只在挂起时创建。当f
调用自身时,调用实际上是尾部递归的,因此没有进一步的堆栈使用(因此没有堆栈溢出)。仅供参考:longlunning
是偏爱单子@谢谢,这很有趣。实际上,longlunning
是对我最初问题的简化——我正在为Scala开发一个类似于库的库,其中Pipe
自然形成一个monad。是的,这种东西通常是某种免费monad的实例化。偏爱一个有点奇怪,因为它通常表示为一个corecursive thunk,但是无论您是否有Free[()=>,R]
或Free[ChunkOfData=>\uUr]
都没有什么根本区别。另外,请注意,这实际上已经存在于标准库中:
def factorial(n: Int, acc: BigInt): LongRunning[BigInt] = {
@annotation.tailrec
def f(n: Int, acc: BigInt): LongRunning[BigInt] =
if (n <= 1)
Result(acc);
else if (n % 10 == 0)
Suspend(() => factorial(n - 1, acc * n))
else
f(n - 1, acc * n)
f(n, acc)
}