深度monadic循环中的scalaz.State堆栈溢出

时间:2015-11-19 12:43:01

标签: scala stack-overflow scalaz state-monad

我尝试Depth-First Searchscalaz的不同实现。

这种遍历应该与深层树状结构一起处理。

主要思想 - 下属元素应该根据某些状态"生成。例如,标记为元素的集合,以避免将来使用它们。

这是我最简单的实现

import scalaz._
import scalaz.syntax.monad._
import scalaz.State._

abstract class DepthFirstState[E, S] {
  def build(elem: E): State[S, List[E]]

  def go(start: E): State[S, List[E]] = for {
    xs ← build(start)
    ys ← xs.traverseSTrampoline(go)
  } yield start :: ys.flatten
}

我们可以创建最简单的算法来测试它如何处理深度搜索

class RangeSearchState extends DepthFirstState[Int, Int] {
  def build(elem: Int) = get[Int] map (limit ⇒ if (elem < limit) List(elem + 1) else Nil)
}

它只是一个降级为链接列表的树,其中每个元素i都有单个子i+1,直到它达到状态编码的limit。虽然州没有改变,但ReaderState更多,但事实并非如此。

现在

new RangeSearchState go 1 run 100

成功构建遍历号码列表。而

new RangeSearchState go 1 run 1000

落在StackOverflowError

是否可以修复DepthFirstState的实现,以便即使在非常深的递归时它也可以在没有StackOverflow的情况下运行?

1 个答案:

答案 0 :(得分:12)

traverseSTrampoline中发生的蹦床可以保护您在遍历期间不会溢出堆栈。例如,这会爆炸:

import scalaz._, scalaz.std.list._, scalaz.syntax.traverse._

(0 to 10000).toList.traverseU(_ => State.get[Unit]).run(())

虽然这不是(请注意traverseStraverseSTrampoline的{​​{1}}相同):

State

但是,在遍历期间,您只能获得此保护,并且在您的情况下,由于递归调用而发生溢出。您可以通过手动进行蹦床来解决此问题:

(0 to 10000).toList.traverseS(_ => State.get[Unit]).run(())

然后:

import scalaz._
import scalaz.std.list._
import scalaz.syntax.traverse._

abstract class DepthFirstState[E, S] {
  type TState[s, a] = StateT[Free.Trampoline, s, a]

  def build(elem: E): TState[S, List[E]]

  def go(start: E): TState[S, List[E]] = for {
    xs <- build(start)
    ys <- xs.traverseU(go)
  } yield start :: ys.flatten
}

class RangeSearchState extends DepthFirstState[Int, Int] {
  def build(elem: Int): TState[Int, List[Int]] =
    MonadState[TState, Int].get.map(limit =>
      if (elem < limit) List(elem + 1) else Nil
    )
}

值得注意的是,此堆栈安全性内置于cats中的val (state, result) = (new RangeSearchState).go(1).run(10000).run

State

详细讨论了这种默认安全选择here