堆栈安全展开

时间:2017-08-01 08:18:27

标签: scala functional-programming

我想为溪流写一个展开。

无法编译,因为它不是尾递归。

@annotation.tailrec
def unfold[A, B](a: A)(f: A => Option[(A, B)]): Stream[B] =
  f(a).map { case (a, b) => b #:: unfold(a)(f) }.getOrElse(Stream.empty)

......也没有真正发挥作用。 (不懒惰)

def unfold[A, B](a: A)(f: A => Option[(A, B)]): Stream[B] = {
  @annotation.tailrec
  def go(state: A, next: Stream[B]): Stream[B] = {
    f(state) match {
      case Some((a, b)) => go(a, b #:: next)
      case None => Stream.empty
    }
  }
  go(a, Stream.empty)
}
// unfold: [A, B](a: A)(f: A => Option[(A, B)])Stream[B]
unfold(0)(i => Some((i+1, i))).take(10).toList
// java.lang.OutOfMemoryError: GC overhead limit exceeded
//   at .$anonfun$res3$1(<console>:18)
//   at .$anonfun$res3$1$adapted(<console>:18)
//   at $$Lambda$1543/299219071.apply(Unknown Source)
//   at .go$1(<console>:19)
//   at .unfold(<console>:24)
//   ... 27 elided

有点难看,通过Iterator

def unfold[A, B](a: A)(f: A => Option[(A, B)]): Stream[B] = {
  new Iterator[B] {
    private var state: Option[A] = Some(a)
    private var nextResult: Option[B] = None

    def next: B =
      nextResult.getOrElse(
        throw new NoSuchElementException("next on empty iterator"))

    def hasNext: Boolean = {
      if (nextResult.isDefined) {
        true
      } else {
        state match {
          case Some(s) =>
            f(s) match {
              case Some((nextState, produced)) =>
                nextResult = Some(produced)
                state = Some(nextState)
                true
              case None => false
            }
          case None => false
        }
      }
    }
  }.toStream
}

1 个答案:

答案 0 :(得分:2)

def unfold[A, B](a: A)(f: A => Option[(A, B)]): Stream[B] =
  f(a).map { case (a, b) => b #:: unfold(a)(f) }.getOrElse(Stream.empty)

这是正确和懒惰的(虽然有些REPL可能会尝试打印所有流)。

Scala流对其tail进行懒惰评估。我认为你不能以尾递归的方式以懒惰的方式影响流的尾部。然而,这是没有意义的,因为懒惰给你堆栈安全:

assert(unfold(0) { _ => Some((0, 0)) } .toString == "Stream(0, ?)")
assert {
    try {
      unfold(0) { _ => Some((0, 0)) }.take(1000000).toList
      true
    } catch {
      case _: StackOverflowError => false
    }
  }