Ecco un pezzo di codice dalla documentazione per fs2 . La funzione go
è ricorsiva. La domanda è: come facciamo a sapere se è stack sicuro e come ragionare se qualche funzione è stack sicuro?
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)
Sarebbe anche sicuro per lo stack se chiamiamo go
da un altro metodo?
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
per usare, ad esempio, la Monad[F]
typeclass: esiste un tailRecM
metodo che ti consente di eseguire esplicitamente il trampolino per garantire che la funzione sia impilabile. Potrei sbagliarmi, ma senza di essa fai affidamento sul fatto di F
essere impilabile da solo (ad esempio se implementa il trampolino internamente), ma non sai mai chi definirà il tuo F
, quindi non dovresti farlo. Se non si ha la garanzia che F
sia stack-safe, utilizzare una classe di tipo che fornisce tailRecM
perché è stack-safe per legge.
@tailrec
annotazione per le funzioni di registrazione della coda. Per altri casi non vi sono garanzie formali in Scala AFAIK. Anche se la funzione stessa è sicura, le altre funzioni che sta chiamando potrebbero non essere: /.