8

I had previously thought that part of the goal of the implementation was to avoid this very problem, so maybe I'm doing something obviously dumb?

Here is some code:

    // 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)

Note: I realize this is silly :) In practice, my command class has many commands, and I have a command which does this same loop...basically, poll some state, if true abort, if false, keep waiting.

I want to avoid the stack overflow that this causes... I THOUGHT this was already trampolined, but I guess I need to manually do it again? Is there a clean way to do it within the free monad way of thinking?

Update:

Thinking further on this, I think the issue isn't the sleep Free Monad but rather the Id.Id monad taht we bind into on evaluation... so I tried something like:

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])

But the problem with this is that it will only evaluate one step. Ideally I would like runFC to block until some condition is satisfied (or in this case, to loop forever until I kill it, but without a stack overflow)

4

2 回答 2

6

Id单子不是蹦床。您最终会在monad 的方法和 free monad的bind方法之间进行无限的相互递归。使用or代替.IdfoldMapTrampolineTaskId

于 2015-04-15T22:59:45.733 回答
2

自从@Apocalisp 的回答以来,已经添加了BindRec类型类和foldMapRec方法,可以直接用于堆栈安全评估Id(或任何其他“尾递归”monad)。有关详细信息,请免费阅读 Stack Safety

于 2016-12-13T20:10:07.570 回答