【发布时间】:2020-03-23 02:01:50
【问题描述】:
这是fs2 文档中的一段代码。函数go 是递归的。问题是我们如何知道它是否是堆栈安全的以及如何推断任何函数是否是堆栈安全的?
import fs2._
// import fs2._
def tk[F[_],O](n: Long): Pipe[F,O,O] = {
def go(s: Stream[F,O], n: Long): Pull[F,O,Unit] = {
s.pull.uncons.flatMap {
case Some((hd,tl)) =>
hd.size match {
case m if m <= n => Pull.output(hd) >> go(tl, n - m)
case m => Pull.output(hd.take(n.toInt)) >> Pull.done
}
case None => Pull.done
}
}
in => go(in,n).stream
}
// tk: [F[_], O](n: Long)fs2.Pipe[F,O,O]
Stream(1,2,3,4).through(tk(2)).toList
// res33: List[Int] = List(1, 2)
如果我们从另一个方法调用go,它是否也是堆栈安全的?
def tk[F[_],O](n: Long): Pipe[F,O,O] = {
def go(s: Stream[F,O], n: Long): Pull[F,O,Unit] = {
s.pull.uncons.flatMap {
case Some((hd,tl)) =>
hd.size match {
case m if m <= n => otherMethod(...)
case m => Pull.output(hd.take(n.toInt)) >> Pull.done
}
case None => Pull.done
}
}
def otherMethod(...) = {
Pull.output(hd) >> go(tl, n - m)
}
in => go(in,n).stream
}
【问题讨论】:
-
不,不完全是。虽然如果这是尾递归的情况,请告诉我,但似乎不是。据我所知,猫会做一些叫做蹦床的魔法来确保堆栈安全。不幸的是,我无法判断一个函数何时被蹦床,何时不是。
-
您可以重写
go以使用例如Monad[F]typeclass -tailRecM方法允许您显式执行蹦床以保证该函数是堆栈安全的。我可能是错的,但没有它,您将依靠F本身的堆栈安全(例如,如果它在内部实现蹦床),但您永远不知道谁将定义您的F,所以您不应该这样做。如果您不能保证F是堆栈安全的,请使用提供tailRecM的类型类,因为它在法律上是堆栈安全的。 -
很容易让编译器用
@tailrec注释来证明它的尾部rec 函数。对于其他情况,Scala AFAIK 没有正式的保证。即使函数本身是安全的,它调用的其他函数也可能不是 :/.
标签: scala functional-programming tail-recursion scala-cats fs2