Ось фрагмент коду з документації на 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]typeclass - існує tailRecMметод, який дозволяє вам виконувати батут явно, щоб гарантувати, що ця функція буде безпечною. Я можу помилятися, але без цього ви покладаєтесь на Fте, щоб захищати їх самостійно (наприклад, якщо він реалізує батут внутрішньо), але ви ніколи не знаєте, хто визначить ваш F, тому не варто цього робити. Якщо у вас немає гарантій щодо Fбезпечності стека, використовуйте клас типу, який надається, tailRecMоскільки він є безпечним для стеків за законом.
@tailrecпримітки для функцій хвостового запису. В інших випадках у Scala AFAIK офіційних гарантій немає. Навіть якщо сама функція безпечна, іншими функціями, які вона викликає, можуть бути: /.