【问题标题】:Continuations and for comprehensions -- what's the incompatibility?延续和理解——有什么不兼容的?
【发布时间】:2012-02-14 14:16:00
【问题描述】:

我是 Scala 的新手,并试图围绕延续 我正在尝试重现 yield return C# 语句。

按照this post,我写了以下代码:

package com.company.scalatest

import scala.util.continuations._;

object GenTest {

  val gen = new Generator[Int] {
    def produce = {
      yieldValue(1)
      yieldValue(2)
      yieldValue(3)
      yieldValue(42)
    }
  }
  // Does not compile :(

  //  val gen2 = new Generator[Int] {
  //    def produce = {
  //      var ints = List(1, 2, 3, 42);
  //
  //      ints.foreach((theInt) => yieldValue(theInt));
  //    }
  //  }

  // But this works?
  val gen3 = new Generator[Int] {
    def produce = {
      var ints = List(1, 2, 3, 42);
      var i = 0;
      while (i < ints.length) {
        yieldValue(ints(i));
        i = i + 1;
      }
    }
  }

  def main(args: Array[String]): Unit = {
    gen.foreach(println);
    //    gen2.foreach(println);
    gen3.foreach(println);
  }
}

abstract class Generator[E] {

  var loopFn: (E => Unit) = null

  def produce(): Unit @cps[Unit]

  def foreach(f: => (E => Unit)): Unit = {
    loopFn = f
    reset[Unit, Unit](produce)
  }

  def yieldValue(value: E) =
    shift { genK: (Unit => Unit) =>
      loopFn(value)
      genK(())
      ()
    }
}

如您所见,gen2 被注释掉,因为它无法编译。由于我可以使用 while 循环轻松迭代列表的内容(请参阅 gen3),因此我希望 foreach 循环也能正常工作。

编译错误如下:

no type parameters for method foreach: (f: Int => B)Unit exist so that 
it can be applied to arguments (Int => Unit @scala.util.continuations.cpsParam[Unit,Unit])  
 --- because --- 
argument expression's type is not compatible with formal parameter type;  
found   : Int => Unit @scala.util.continuations.cpsParam[Unit,Unit]  
required: Int => ?B 

为什么我会收到此错误,有没有办法使用比 while 循环更简洁的方法来解决此问题?

谢谢

【问题讨论】:

  • 我不是在寻找一个答案,告诉我如何使用 Scala 延续来重现“收益回报”。我正在寻找示例中的“gen2”不起作用的原因。 “收益回报”只是我遇到此问题的上下文。
  • 我将主题更改为更适合您实际问题的内容。
  • gen2 不起作用的答案主要与延续编译器插件如何将移位和重置转换为底层 ControlContext 实例有关。我将在今天晚些时候整理出一个正确的答案,但现在我就这个主题写了一个简介:earldouglas.com/monadic-continuations-in-scala

标签: scala generator yield continuations continuation-passing


【解决方案1】:

首先让我们看看如何让gen2 编译。

object CpsConversions {

  import scala.collection.IterableLike
  import scala.util.continuations._

  implicit def cpsIterable[A, Repr](xs: IterableLike[A, Repr]) = new {
    def cps = new {
      def foreach[B](f: A => Any@cpsParam[Unit, Unit]): Unit@cpsParam[Unit, Unit] = {
        val it = xs.iterator
        while(it.hasNext) f(it.next)
      }
    }
  }
}

object GenTest {

  import CpsConversions.cpsIterable
  val gen2 = new Generator[Int] {
    def produce = {
      var ints = List(1, 2, 3, 42)
      ints.cps.foreach((theInt) => yieldValue(theInt))
    }
  }

现在让我们来看看发生了什么。原来的gen2 在下面一行编译失败:

ints.foreach((theInt) => yieldValue(theInt))

由于yieldValue 的类型包括@cpsParam 注释,延续插件将传递给foreach 方法的函数转换为以下类型之一:

Int => Unit @cpsParam[Unit,Unit]

List[Int] 的层次结构中,您会看到foreach 定义为:

foreach [U] (f: (Int) ⇒ U): Unit

这是一个问题,因为类型不匹配并且 Scala 不知道如何从 Int =&gt; U 获取到 Int =&gt; Unit @cpsParam[Unit,Unit]。为了解决这个问题,我在隐式转换中添加了 foreach 的 CPS 版本,您可以通过在任何 IterableLike 上调用 cps 来访问它。

如果这种隐式转换可以在没有显式 cps 调用的情况下完成,那将是非常好的,但我还没有找到一种方法让 Scala 编译器认识到这种隐式转换的适用性来拉皮条新的 foreach到你的名单上。这可能与编译器使用延续插件的顺序有关,但我对这个过程知之甚少,无法确定。

所以这对foreach 来说很好。您的问题提到了理解,这将需要定义 filtermapflatMap 中的任何一个(取决于您的理解中发生了什么)。我已经在我上面评论的链接中实现了这些,它扩展了上面的 CpsConversions 对象以允许一般理解。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-11-15
    • 2011-10-22
    • 2018-01-26
    • 2017-10-11
    • 2011-04-26
    • 2012-05-14
    • 2016-11-09
    相关资源
    最近更新 更多