我以前认为实施目标的一部分是为了避免这个问题,所以也许我做了一些明显愚蠢的事情?
以下是一些代码:
// Stack overflow
import scalaz._
sealed trait Command[T]
case class Wait(ms: Long) extends Command[Unit]
case object Evaluator extends (Command ~> Id.Id) {
override def apply[T](cmd: Command[T]) = cmd match {
case Wait(t) => Thread.sleep(t)
}
}
object Api {
def sleep(ms: Long): Free.FreeC[Command, Unit] = Free.liftFC(Wait(ms))
}
val sleep: Free.FreeC[Command, Unit] =
Api.sleep(1).flatMap { _ => sleep }
Free.runFC(sleep)(Evaluator)
注意:我意识到这很愚蠢:)在实践中,我的命令类有很多命令,我有一个命令,它执行相同的循环...基本上,轮询一些状态,如果真正中止,如果为false,继续等待
我想避免这导致的堆栈溢出......我认为这已经蹦了过来,但我想我需要再次手动执行此操作?是否有一种干净的方式在免费的monad思维方式中做到这一点?
更新
进一步思考这个问题,我认为这个问题不是睡眠免费Monad,而是我们在评估中绑定的Id.Id monad ...所以我尝试了类似的东西:
case object Evaluator2 extends (Command ~> ({ type t[x] = Free[Id.Id, x] })#t) {
override def apply[T](cmd: Command[T]) = cmd match {
case Wait(t) => Thread.sleep(t); Free.liftF[Id.Id, Unit](())
}
}
Free.runFC[Command, ({ type t[x] = Free[Id.Id, x] })#t, Unit](sleep)(Evaluator2)(Free.freeMonad[Id.Id])
但问题在于它只评估一步。理想情况下,我希望runFC阻塞,直到满足某些条件(或者在这种情况下,永远循环直到我杀死它,但没有堆栈溢出)
答案 0 :(得分:6)
Id
monad不是蹦床。您最终会在bind
monad的Id
方法和免费monad的foldMap
方法之间进行无限的相互递归。使用Trampoline
或Task
代替Id
。
答案 1 :(得分:2)
自@ Apocalisp的答案以来,添加了BindRec
类型类和foldMapRec
方法,可以直接用于Id
(或任何其他“尾递归”的堆栈安全评估“monad)。有关详细信息,请阅读Stack Safety for Free。