Oto fragment kodu z dokumentacji dla fs2 . Funkcja go
jest rekurencyjna. Pytanie brzmi: skąd wiemy, czy można je bezpiecznie nakładać i jak uzasadnić, czy jakakolwiek funkcja jest bezpieczna w stosie?
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)
Czy byłoby również bezpieczne w stosie, gdybyśmy zadzwonili go
z innej metody?
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
aby użyć np. Monad[F]
Klasy - istnieje tailRecM
metoda umożliwiająca jawne wykonanie trampoliny, aby zagwarantować, że funkcja będzie bezpieczna w stosie. Mogę się mylić, ale bez tego polegasz na F
tym, że sam jesteś bezpieczny w stosie (np. Jeśli implementuje wewnętrznie trampolinę), ale nigdy nie wiesz, kto cię zdefiniuje F
, więc nie powinieneś tego robić. Jeśli nie masz żadnej gwarancji, że F
jest bezpieczny w stosach, użyj klasy typu, która zapewnia, tailRecM
że zgodnie z prawem jest bezpieczny w stosach.
@tailrec
adnotacji dotyczących funkcji nagrywania z tyłu. W innych przypadkach nie ma formalnych gwarancji w Scala AFAIK. Nawet jeśli sama funkcja jest bezpieczna, inne wywoływane funkcje mogą nie być: /.