Hier ist ein Teil des Codes aus der Dokumentation für fs2 . Die Funktion go
ist rekursiv. Die Frage ist, woher wissen wir, ob es stapelsicher ist und wie man begründet, ob eine Funktion stapelsicher ist?
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)
Wäre es auch stapelsicher, wenn wir go
von einer anderen Methode aus aufrufen ?
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
}
scala
functional-programming
tail-recursion
scala-cats
fs2
Lev Denisov
quelle
quelle
go
, um z. B. eineMonad[F]
Typklasse zu verwenden. Es gibt einetailRecM
Methode, mit der Sie das Trampolin explizit ausführen können, um sicherzustellen, dass die Funktion stapelsicher ist. Ich könnte mich irren, aber ohne sie verlassen Sie sich darauf,F
dass Sie selbst stapelsicher sind (z. B. wenn es Trampolin intern implementiert), aber Sie wissen nie, wer Ihr definiertF
, also sollten Sie dies nicht tun. Wenn Sie keine stapelsichere Garantie habenF
, verwenden Sie eine Typklasse, die dies vorsieht,tailRecM
da sie gesetzlich stapelsicher ist.@tailrec
Anmerkungen für Tail Rec-Funktionen beweisen zu lassen . Für andere Fälle gibt es in der Scala AFAIK keine formellen Garantien. Selbst wenn die Funktion selbst sicher ist, sind die anderen Funktionen, die sie aufruft, möglicherweise nicht: /.Antworten:
Meine vorherige Antwort hier enthält einige Hintergrundinformationen, die nützlich sein könnten. Die Grundidee ist, dass einige Effekttypen
flatMap
Implementierungen haben, die eine stapelsichere Rekursion direkt unterstützen. Sie könnenflatMap
Aufrufe entweder explizit oder durch Rekursion so tief verschachteln, wie Sie möchten, und Sie werden den Stapel nicht überlaufen lassen.Bei einigen Effekttypen ist es
flatMap
aufgrund der Semantik des Effekts nicht möglich , stapelsicher zu sein. In anderen Fällen ist es möglicherweise möglich, einen Stack-Safe zu schreibenflatMap
, aber die Implementierer haben möglicherweise aufgrund der Leistung oder anderer Überlegungen entschieden, dies nicht zu tun.Leider gibt es keine Standardmethode (oder sogar keine konventionelle Methode), um festzustellen, ob die
flatMap
für einen bestimmten Typ stapelsicher ist. Cats enthält einetailRecM
Operation, die eine stapelsichere monadische Rekursion für jeden rechtmäßigen monadischen Effekttyp bereitstellen sollte, und manchmal kann ein Blick auf einetailRecM
Implementierung, die als rechtmäßig bekannt ist, einige Hinweise darauf geben, ob aflatMap
stapelsicher ist. Im Fall desPull
es sieht aus wie diese :Dies
tailRecM
ist Rekursion nur durchflatMap
, und wir wissen , dassPull
‚sMonad
Instanz rechtmäßig ist , was ziemlich gut Beweise dafür, dassPull
‘ sflatMap
heißt Stapel sicher. Der einzige komplizierende Faktor hierbei ist, dass die Instanz fürPull
eineApplicativeError
Einschränkung hatF
,Pull
dieflatMap
dies nicht tut, aber in diesem Fall ändert dies nichts.Die
tk
Implementierung hier ist also stapelsicher, daflatMap
onPull
stapelsicher ist, und das wissen wir aus der Betrachtung dertailRecM
Implementierung. (Wenn wir etwas tiefer graben, können wir herausfinden, dass diesflatMap
stapelsicher ist, daPull
es sich im Wesentlichen um eine Hülle handelt, fürFreeC
die Trampoline verwendet werden .)Es wäre wahrscheinlich nicht besonders schwer,
tk
in Bezug auf neu zu schreibentailRecM
, obwohl wir die ansonsten unnötigeApplicativeError
Einschränkung hinzufügen müssten . Ich vermute, die Autoren der Dokumentation haben sich aus Gründen der Klarheit dafür entschieden, dies nicht zu tun, und weil sie wusstenPull
, dassflatMap
es in Ordnung ist.Update: Hier ist eine ziemlich mechanische
tailRecM
Übersetzung:Beachten Sie, dass es keine explizite Rekursion gibt.
Die Antwort auf Ihre zweite Frage hängt davon ab, wie die andere Methode aussieht. Im Fall Ihres speziellen Beispiels
>>
werden jedoch nur mehrflatMap
Ebenen erstellt, sodass dies in Ordnung sein sollte.Um Ihre Frage allgemeiner zu beantworten, ist dieses ganze Thema in Scala ein verwirrendes Durcheinander. Sie sollten sich nicht wie oben beschrieben mit Implementierungen befassen müssen, um zu wissen, ob ein Typ eine stapelsichere monadische Rekursion unterstützt oder nicht. Bessere Konventionen in Bezug auf die Dokumentation wären hier eine Hilfe, aber leider machen wir das nicht sehr gut. Sie könnten immer
tailRecM
"sicher" sein (was SieF[_]
sowieso tun möchten, wenn das generisch ist), aber selbst dann vertrauen Sie darauf, dass dieMonad
Implementierung rechtmäßig ist.Zusammenfassend lässt sich sagen, dass es rundum eine schlechte Situation ist. In sensiblen Situationen sollten Sie auf jeden Fall Ihre eigenen Tests schreiben, um zu überprüfen, ob solche Implementierungen stapelsicher sind.
quelle
go
von einer anderen Methode aus aufrufen , was kann dazu führen, dass der Stapel unsicher wird? Wenn wir vor dem Aufruf einige nicht rekursive Berechnungen durchführen,Pull.output(hd) >> go(tl, n - m)
ist das in Ordnung?ContT
‚sflatMap
ist eigentlich Stack-safe (über eineDefer
Einschränkung des zugrunde liegenden Typs). Ich dachte eher an so etwas wieList
, wo das RekursierenflatMap
nicht stapelsicher ist (es hat jedoch ein GesetztailRecM
).