Aquí hay un fragmento de código de la documentación para fs2 . La función go
es recursiva. La pregunta es ¿cómo sabemos si es seguro para la pila y cómo razonar si alguna función es segura para la pila?
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)
¿También sería seguro para la pila si llamamos go
desde otro método?
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
para usar, por ejemplo, Monad[F]
typeclass: hay un tailRecM
método que le permite realizar el trampolín explícitamente para garantizar que la función sea segura para la pila. Puede que me equivoque, pero sin eso F
dependes de la seguridad de la pila por sí solo (por ejemplo, si implementa el trampolín internamente), pero nunca sabes quién definirá tu F
, así que no deberías hacer esto. Si no tiene garantía de que F
sea apilable, use una clase de tipo que proporcione tailRecM
porque es apta para la ley.
@tailrec
anotaciones para las funciones de grabación de cola. Para otros casos no hay garantías formales en Scala AFAIK. Incluso si la función en sí es segura, las otras funciones que está llamando podrían no ser: /.