Aquí hay un fragmento de código de la documentación para fs2 . La función goes recursiva. La pregunta es ¿cómo sabemos si es seguro para la pila y cómo razonar si alguna función es segura para la pila?
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)¿También sería seguro para la pila si llamamos godesde otro método?
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
fuente
                
                fuente

gopara usar, por ejemplo,Monad[F]typeclass: hay untailRecMmétodo que le permite realizar el trampolín explícitamente para garantizar que la función sea segura para la pila. Puede que me equivoque, pero sin esoFdependes de la seguridad de la pila por sí solo (por ejemplo, si implementa el trampolín internamente), pero nunca sabes quién definirá tuF, así que no deberías hacer esto. Si no tiene garantía de queFsea apilable, use una clase de tipo que proporcionetailRecMporque es apta para la ley.@tailrecanotaciones para las funciones de grabación de cola. Para otros casos no hay garantías formales en Scala AFAIK. Incluso si la función en sí es segura, las otras funciones que está llamando podrían no ser: /.Respuestas:
Mi respuesta anterior aquí proporciona información de fondo que podría ser útil. La idea básica es que algunos tipos de efectos tienen
flatMapimplementaciones que admiten directamente la recursividad segura para la pila; puede anidarflatMapllamadas explícitamente o mediante recursión tan profundamente como desee y no desbordará la pila.Para algunos tipos de efectos no es posible
flatMapapilar con seguridad debido a la semántica del efecto. En otros casos, es posible escribir una pila seguraflatMap, pero los implementadores pueden haber decidido no hacerlo debido al rendimiento u otras consideraciones.Desafortunadamente, no hay una forma estándar (o incluso convencional) de saber si
flatMapun tipo determinado es apilable. Los gatos incluyen unatailRecMoperación que debería proporcionar una recidiva monádica segura para cualquier tipo de efecto monádico legal, y a veces mirar unatailRecMimplementación que se sabe que es legal puede proporcionar algunas pistas sobre si unaflatMappila es segura para la pila. En el caso dePullque se parece a esto :Esto
tailRecMes sólo a través recursivaflatMap, y sabemos quePull'sMonadejemplo, es legal , lo cual es bastante buena evidencia de quePull' sflatMapes pila de fallos. El único factor de complicación aquí es que la instancia dePulltiene unaApplicativeErrorrestricción enFquePull'sflatMapno lo hace, pero en este caso eso no cambia nada.Entonces, la
tkimplementación aquí es seguraflatMappara laPullpila porque on es segura para la pila, y lo sabemos al observar sutailRecMimplementación. (Si profundizáramos un poco más, podríamos descubrir queflatMapes apta para apilar porquePulles esencialmente un envoltorio paraFreeC, que está trampolinado ).Probablemente no sería terriblemente difícil reescribir
tken términos detailRecM, aunque tendríamos que agregar laApplicativeErrorrestricción innecesaria . Supongo que los autores de la documentación decidió no hacerlo por razones de claridad, y porque sabíanPull'sflatMapestá muy bien.Actualización: aquí hay una
tailRecMtraducción bastante mecánica :Tenga en cuenta que no hay una recursión explícita.
La respuesta a su segunda pregunta depende de cómo se ve el otro método, pero en el caso de su ejemplo específico,
>>solo dará como resultado másflatMapcapas, por lo que debería estar bien.Para abordar su pregunta de manera más general, todo este tema es un desastre confuso en Scala. No debería tener que profundizar en las implementaciones como lo hicimos anteriormente solo para saber si un tipo admite la recursión monádica segura para la pila o no. Las mejores convenciones sobre documentación serían una ayuda aquí, pero desafortunadamente no estamos haciendo un buen trabajo al respecto. Siempre se puede usar
tailRecMpara ser "seguro" (que es lo que querrá hacer cuandoF[_]sea genérico, de todos modos), pero aun así confía en que laMonadimplementación es legal.En resumen: es una mala situación en general, y en situaciones delicadas definitivamente debes escribir tus propias pruebas para verificar que implementaciones como esta sean aptas para la pila.
fuente
godesde otro método, ¿qué puede hacer que se apile inseguro? Si hacemos algunos cálculos no recursivos antes de llamar, ¿Pull.output(hd) >> go(tl, n - m)está bien?ContT'sflatMapes realmente pila de fallos (a través de unaDeferrestricción sobre el tipo subyacente). Estaba pensando más en algo asíList, donde recurrirflatMapno es seguro para la pila (aunque sí es legaltailRecM).