【问题标题】:Implementing yield (yield return) using Scala continuations使用 Scala 延续实现收益(收益回报)
【发布时间】:2011-01-13 04:46:46
【问题描述】:

如何使用 Scala 延续实现 C# yield return?我希望能够以相同的风格编写 Scala Iterators。 this Scala news post 的 cmets 中有一个刺,但它不起作用(尝试使用 Scala 2.8.0 beta)。 related question 中的答案表明这是可能的,但尽管我已经玩了一段时间的定界延续,但我似乎无法完全理解如何做到这一点。

【问题讨论】:

标签: scala scala-2.8 yield continuations yield-return


【解决方案1】:

在我们引入延续之前,我们需要构建一些基础设施。 下面是一个在Iteration 对象上运行的trampoline。 迭代是一种计算,它可以是Yield 一个新值,也可以是Done

sealed trait Iteration[+R]
case class Yield[+R](result: R, next: () => Iteration[R]) extends Iteration[R]
case object Done extends Iteration[Nothing]

def trampoline[R](body: => Iteration[R]): Iterator[R] = {
  def loop(thunk: () => Iteration[R]): Stream[R] = {
    thunk.apply match {
      case Yield(result, next) => Stream.cons(result, loop(next))
      case Done => Stream.empty
    }
  }
  loop(() => body).iterator
}

蹦床使用内部循环将Iteration 对象的序列转换为Stream。 然后我们通过在结果流对象上调用iterator 来获得Iterator。 通过使用Stream,我们的评估是惰性的;在需要之前,我们不会评估我们的下一次迭代。

蹦床可以用来直接构建迭代器。

val itr1 = trampoline {
  Yield(1, () => Yield(2, () => Yield(3, () => Done)))
}

for (i <- itr1) { println(i) }

这写起来很糟糕,所以让我们使用定界延续来自动创建我们的Iteration 对象。

我们使用shiftreset 运算符将计算分解为Iterations, 然后使用trampolineIterations 变成Iterator

import scala.continuations._
import scala.continuations.ControlContext.{shift,reset}

def iterator[R](body: => Unit @cps[Iteration[R],Iteration[R]]): Iterator[R] =
  trampoline {
    reset[Iteration[R],Iteration[R]] { body ; Done }
  }

def yld[R](result: R): Unit @cps[Iteration[R],Iteration[R]] =
  shift((k: Unit => Iteration[R]) => Yield(result, () => k(())))

现在我们可以重写我们的例子了。

val itr2 = iterator[Int] {
  yld(1)
  yld(2)
  yld(3)
}

for (i <- itr2) { println(i) }

好多了!

下面是来自C# reference pageyield 示例,它显示了一些更高级的用法。 这些类型可能有点难以适应,但一切正常。

def power(number: Int, exponent: Int): Iterator[Int] = iterator[Int] {
  def loop(result: Int, counter: Int): Unit @cps[Iteration[Int],Iteration[Int]] = {
    if (counter < exponent) {
      yld(result)
      loop(result * number, counter + 1)
    }
  }
  loop(number, 0)
}

for (i <- power(2, 8)) { println(i) }

【讨论】:

  • 我想查看迭代器 yld 的 scalac -print 输出以及对 itr2 的分配。有插件的人可以将此添加到答案中吗?
  • 我只是想应用这个,所以我的代码运行起来很方便。输出见gist.github.com/297230(滚动到底部)。
  • 我会将iterator 重命名为yldIterator 或类似名称,以避免混淆。 :-)
  • 刚刚看到这个被改编成grizzled scala库了。
  • Grizzled 在 1.1.6 中删除了生成器,因为它依赖于“不受支持和未维护的 Scala 延续插件”。在这里阅读:github.com/bmc/grizzled-scala/blob/master/CHANGELOG.md
【解决方案2】:

在玩了几个小时之后,我设法找到了一种方法来做到这一点。我认为这比我迄今为止看到的所有其他解决方案更容易理解,尽管后来我非常感谢 Rich 和 Miles' 的解决方案。

def loopWhile(cond: =>Boolean)(body: =>(Unit @suspendable)): Unit @suspendable = {
  if (cond) {
    body
    loopWhile(cond)(body)
  }
}

  class Gen {
    var prodCont: Unit => Unit = { x: Unit => prod }
    var nextVal = 0
    def yld(i: Int) = shift { k: (Unit => Unit) => nextVal = i; prodCont = k }
    def next = { prodCont(); nextVal }
    def prod = {
      reset {
        // following is generator logic; can be refactored out generically
        var i = 0
        i += 1
        yld(i)
        i += 1
        yld(i)
        // scala continuations plugin can't handle while loops, so need own construct
        loopWhile (true) {
          i += 1
          yld(i)
        }
      }
    }
  }
  val it = new Gen
  println(it.next)
  println(it.next)
  println(it.next)

【讨论】:

  • Scala continuation 无法处理 while 循环?哎哟!
  • 确实如此。 :( 希望这是一个正在进行的工作,但我相信理解绝对与 shift 不兼容,因为这意味着撕开地图/foreach/等。
  • 不再。从 while 循环中调用 cps 代码已经有一段时间了。不过,理解仍然不受支持(我真的不认为他们会获得支持)
  • @PrzemekPokrywka:……除非 Scala 拥有自己的虚拟机,或者 Java 12 发布,或者其他什么。
猜你喜欢
  • 1970-01-01
  • 2012-01-14
  • 1970-01-01
  • 2011-05-27
  • 2013-07-22
  • 2011-08-08
  • 2010-09-29
  • 1970-01-01
相关资源
最近更新 更多