以下は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]
tailRecM
F
F
F
tailRecM
@tailrec
にtail rec関数の注釈を付けて簡単に証明させることができます。その他の場合、Scala AFAIKには正式な保証はありません。関数自体が安全であっても、それが呼び出している他の関数は:/ではない場合があります。