几周前 Dragisa Krsmanovica question here 询问了如何在 Scalaz 7 中使用 free monad 来避免这种情况下的堆栈溢出(我稍微修改了他的代码):

import scalaz._, Scalaz._

def setS(i: Int): State[List[Int], Unit] = modify(i :: _)

val s = (1 to 100000).foldLeft(state[List[Int], Unit](())) {
  case (st, i) => st.flatMap(_ => setS(i))
}

s(Nil)

我认为 that just lifting a trampoline into StateT 应该可以工作:
import Free.Trampoline

val s = (1 to 100000).foldLeft(state[List[Int], Unit](()).lift[Trampoline]) {
  case (st, i) => st.flatMap(_ => setS(i).lift[Trampoline])
}

s(Nil).run

但它仍然炸毁了堆栈,所以我只是将其作为评论发布。

Dave Stevens 只是 pointed out 与应用 *> 而不是 monadic flatMap 的排序实际上工作得很好:
val s = (1 to 100000).foldLeft(state[List[Int], Unit](()).lift[Trampoline]) {
  case (st, i) => st *> setS(i).lift[Trampoline]
}

s(Nil).run

(嗯,当然它 super 慢,因为这是你在 Scala 中做任何有趣的事情所付出的代价,但至少没有堆栈溢出。)

这里发生了什么?我不认为这种差异可能存在原则性原因,但我真的不知道实现过程中会发生什么,目前没有时间深入研究。但我很好奇,如果其他人知道会很酷。

最佳答案

Mandubian 是正确的,StateT 的 flatMap 不允许您绕过堆栈累积,因为在调用包装的 monad 的绑定(bind)之前立即创建了新的 StateT(在您的情况下这将是 Free[Function0])。

因此 Trampoline 无济于事,但是 State 的仿函数上的 Free Monad 是确保堆栈安全的一种方法。

我们想从 State[List[Int],Unit] 到 Free[a[State[List[Int],a],Unit] 并且我们的 flatMap 调用将是 Free 的 flatMap(除了创建自由数据结构)。

val s = (1 to 100000).foldLeft(
    Free.liftF[({ type l[a] = State[List[Int],a]})#l,Unit](state[List[Int], Unit](()))) {
      case (st, i) => st.flatMap(_ =>
          Free.liftF[({ type l[a] = State[List[Int],a]})#l,Unit](setS(i)))
    }

现在我们已经构建了一个自由数据结构,我们可以轻松地将状态线程化,如下所示:
s.foldRun(List[Int]())( (a,b) => b(a) )

调用 LiftF 相当难看,所以我有一个 PR 来让 State 和 Kleisli monads 更容易,所以希望将来不需要类型 lambdas。

编辑:公关接受了,所以现在我们有了
val s = (1 to 100000).foldLeft(state[List[Int], Unit](()).liftF) {
      case (st, i) => st.flatMap(_ => setS(i).liftF)
}

关于scala - Applicative 与 monadic 组合器和 Scalaz 中的自由 monad,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/24151771/

10-15 11:24