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 goda 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
}
goper usare, ad esempio, la Monad[F]typeclass: esiste un tailRecMmetodo 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 Fessere 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 Fsia stack-safe, utilizzare una classe di tipo che fornisce tailRecMperché è stack-safe per legge.
@tailrecannotazione 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: /.