Ось фрагмент коду з документації на 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 офіційних гарантій немає. Навіть якщо сама функція безпечна, іншими функціями, які вона викликає, можуть бути: /.