【问题标题】:How to handle nested structure when traversing with state monad遍历状态单子时如何处理嵌套结构
【发布时间】:2012-12-17 19:55:46
【问题描述】:

我有一个嵌套结构,我正在使用 scalaz 状态单子将其转换为 XML。在我必须处理多级嵌套结构之前,这很有效。这是一个类似于我正在做的简化示例。给定以下 ADT:

sealed trait Nested
case object Leaf extends Nested
case class Foo(inner: Nested) extends Nested
case class Bar(inner: Nested) extends Nested

我使用状态 monad 编写了一个转换器对象(假设 Scalaz7 和以下导入 import scalaz.{Node => _, _}; import Scalaz._; import scala.xml._):

case class Parents(foos: Int, bars: Int)
type ParentsS[X] = State[Parents, X]

def convertFoo(foo: Foo): ParentsS[Seq[Node]] = for {
  parents <- init[Parents]
  _ <- put[Parents](Parents(parents.foos + 1, parents.bars))
  inner <- convert(foo.inner)
  _ <- put[Parents](parents)
} yield <foo count={ parents.foos.toString }/>.copy(child=inner)

def convertBar(bar: Bar): ParentsS[Seq[Node]] = for {
  parents <- init[Parents]
  _ <- put[Parents](Parents(parents.foos, parents.bars + 1))
  inner <- convert(bar.inner)
  _ <- put[Parents](parents)
} yield <bar count={ parents.bars.toString }/>.copy(child=inner)

def convert(nested: Nested): ParentsS[Seq[Node]] = nested match {
  case Leaf => Seq[Node]().point[ParentsS]
  case foo@Foo(_) => convertFoo(foo)
  case bar@Bar(_) => convertBar(bar)
}

def nested(n: Int): Nested =
  if (n == 0) Leaf
  else {
    if (n % 2 == 0) Bar(nested(n - 1))
    else Foo(nested(n - 1))
  }

根据我的堆栈设置,convert(nested(1000)).apply(Parents(0, 0)) 会在转换过程中导致堆栈溢出。 (较高的值会导致nested 溢出,但这可以忽略,因为我刚刚为这个问题创建了nested。):

    at scalaz.IdInstances$$anon$1.bind(Id.scala:20)
    at scalaz.StateT$$anonfun$flatMap$1.apply(StateT.scala:48)
    at scalaz.StateT$$anon$7.apply(StateT.scala:72)
    at scalaz.StateT$$anonfun$flatMap$1.apply(StateT.scala:48)
    at scalaz.StateT$$anon$7.apply(StateT.scala:72)
    at scalaz.StateT$$anonfun$flatMap$1$$anonfun$apply$2.apply(StateT.scala:49)
    at scalaz.StateT$$anonfun$flatMap$1$$anonfun$apply$2.apply(StateT.scala:48)

我的问题是 - 在scalaz.stateT 中避免堆栈溢出的最佳方法是什么?如果使 XML 序列化逻辑更易于遵循和排除故障,我想继续使用 state monad,就像在我的真实示例中一样(实际输入结构是从实时调试会话中检索到的 JDI 镜像objects and arrays,内部值是嵌套字段值) .

编辑:取出嵌套堆栈问题:

import util.control.TailCalls
def nested2(n: Int, acc: Nested = Leaf): TailCalls.TailRec[Nested] =
  if (n == 0) TailCalls.done(acc)
  else TailCalls.tailcall(nested2(n - 1, if (n % 2 == 0) Bar(acc) else Foo(acc)))

【问题讨论】:

  • 我想起了我收藏的这个话题。我刚刚注意到你开始了它 - groups.google.com/forum/#!topic/scalaz/QPUs6TWTAm4 我一直使用 StateT,但是当我知道我将要遍历 200 多个左右时,最终会得到一些不太优雅的东西。
  • 我只是通过运行 n=1000 的嵌套方法(不使用任何 Scalaz 代码)获得了 StackOverflow。
  • @paradigmatic,使用我刚刚添加的蹦床nested2。我怀疑我的问题的答案也是蹦床convert,但这对我来说如何优雅地做到这一点并不明显。

标签: scala stack-overflow scalaz state-monad


【解决方案1】:

蹦床可以帮助您避免这里的堆栈溢出。首先是相同的设置:

sealed trait Nested
case object Leaf extends Nested
case class Foo(inner: Nested) extends Nested
case class Bar(inner: Nested) extends Nested

import scalaz.{Node => _, _}; import Scalaz._;
import scala.util.control.TailCalls, scala.xml._

case class Parents(foos: Int, bars: Int)

def nested(n: Int, acc: Nested = Leaf): TailCalls.TailRec[Nested] =
  if (n == 0) TailCalls.done(acc) else TailCalls.tailcall(
    nested(n - 1, if (n % 2 == 0) Bar(acc) else Foo(acc))
  )

一些略有不同的类型别名:

type TrampolinedState[S, A] = StateT[Free.Trampoline, S, A]
type ParentsS[A] = TrampolinedState[Parents, A]

为方便起见,我们将导入 MonadState 实例的方法:

val monadState = MonadState[TrampolinedState, Parents]
import monadState._

剩下的其实更简洁一点,因为我们不需要put上的类型参数等:

def convertFoo(foo: Foo): ParentsS[Seq[Node]] = for {
  parents <- init
  _ <- put(Parents(parents.foos + 1, parents.bars))
  inner <- convert(foo.inner)
  _ <- put(parents)
} yield <foo count={ parents.foos.toString }/>.copy(child=inner)

def convertBar(bar: Bar): ParentsS[Seq[Node]] = for {
  parents <- init
  _ <- put(Parents(parents.foos, parents.bars + 1))
  inner <- convert(bar.inner)
  _ <- put(parents)
} yield <bar count={ parents.bars.toString }/>.copy(child=inner)

def convert(nested: Nested): ParentsS[Seq[Node]] = nested match {
  case Leaf => Seq[Node]().point[ParentsS]
  case foo@Foo(_) => convertFoo(foo)
  case bar@Bar(_) => convertBar(bar)
}

现在我们只需运行以下命令(例如):

convert(nested(2000).result).apply(Parents(0, 0)).run

这远远超出了香草State 解决方案开始在我的机器上阻塞的地步。

【讨论】:

  • 谢谢!希望我可以+10。我在之前执行List[A].traverse[({type λ[α] = State[S, α]})#λ, A] 的几个地方使用了这种通用方法来防止 SO。花了一点时间才弄清楚如何使用 Scalaz 6 进行这项工作,但我最终明白了。
猜你喜欢
  • 2021-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-06
  • 2011-09-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多