Scala延续:序列中有许多变化

时间:2012-02-28 17:46:04

标签: scala continuations continuation-passing

我一直试图用scala continuation来解决复杂的输入问题。我一直在阅读我能找到的所有材料,包括延续包中的参考文档。我认为我已经在某种程度上弄明白了,当你想到它时它会变得很有意义。

我认为我对它的理解(以及我的一些问题)可以通过这个程序得到最好的总结:

package com.whatever;
import scala.util.continuations._;

object methods {

  /* The method takes an Int as its parameter.  Theoretically, at some point in the future,
   * it will return a Float to the remainder of the continuation.  This example does it
   * immediately but doesn't have to (for example it could be calling a network service
   * to do the transformation)
   * 
   * Float @cpsParam[Unit,Float] means that whatever part of the reset{} that is captured
   * as a closure should receive a Float and needn't return anything (would it be meaningful
   * if Unit were something else?)
   * 
   * The reason I have to return 0.toFloat is so the compiler can properly type the
   * method.  That zero will never go anywhere.  Is this a sign I'm doing it wrong?
   */
  def method1(param:Int): Float @cpsParam[Unit,Float] = shift { cb:(Float=>Unit) =>
    cb(param.toFloat); 
    0.toFloat;
  }

  /* This method is basically identical but returns a String instead of a Float (Again,
   * theoretically this would be done by a network service and cb would be called at some
   * point in the future.
   */
  def method2(param:Int): String @cpsParam[Unit,String] = shift { cb:(String=>Unit) =>
    cb(param.toString);
    ""
  }
}

object Main {
  def main(args:Array[String]):Unit = {
    reset {
      val f = methods.method1(5);
      println(f);
    }
  }
}

顺便说一句,StackOverflow没有突出显示scala是犯罪行为!(我站得更正;它确实做得很好,但不是在实时预览中)

我的问题如下:

  1. 根据上述计划中的评论,我对scala的CPS的理解缺少什么?是否有一种情况,您不希望在UnitB @cpsParam[B,C]作为"5.0"
  2. 以上程序编译并运行(打印出reset)。但是,我现在遇到的问题是,当我更改method2块以尝试在method1之后调用reset { val f = methods.method1(5); println(f); val s = methods.method2(42); println(s); } 时,我遇到了这个问题:
  3. (显然你不能在列表后面有代码块)

    illegal answer type modification: scala.util.continuations.cpsParam[Unit,Float] andThen scala.util.continuations.cpsParam[Unit,String]
    

    当我这样做时(这看起来很简单),我在重置时得到以下编译器错误(这是scala 2.10里程碑2):

    {{1}}

    我将此解释为“你的第一个班次返回一个浮点数,你的第二个班次返回一个字符串,你不能这样做。”这准确吗?这是否意味着你不能使用CPS按顺序执行两个(或更多)事情,除非它们具有相同的返回类型?因为这似乎是一种严重的限制。我猜测我要么是1)缺少允许你这样做的东西,或者B)遗漏了一些明显的原因,为什么CPS不可能发生这种情况。但是它是哪一个?

    为了理解scala的CPS,我开始觉得你不需要成为博士后学生。但我当然还没有。

1 个答案:

答案 0 :(得分:4)

在我提出这个问题之后,我做了更多的研究,我想我现在能够回答我自己的问题(我希望这不是一个失礼)。

我做了三件事让我理解了这个问题,而且我认为任何在scala延续中遇到问题的人都会采取以下措施:

  1. 阅读the original academic paper about scala's continuations。它非常干燥,我怀疑它主要是一群疯子的乱语,但它也非常有用,因为它让你深入了解编译器如何转换你的继续驱动的代码和打字和这样做会导致纯度问题。
  2. 以回调传递方式重写您的代码。这是您可以做的最重要的事情,以便真正了解继续流及其类型的流程
  3. 检查,我的意思是检查 shift的类型签名,并注意它正在做什么。这将带你进入我的顿悟。
  4. 在我的情况下,我输入了@cpsParamcb参数shift都错了。我将解释我是如何弄清楚我做错了什么的,这样任何像我一样愚蠢的人都可以遵循相同的步骤,并希望在延续编译器驱动它们时获得一些见解 mad

    第1步

    我阅读了上述文章。大约十几次。我仍然很清楚。但我所理解的是非常有帮助的。

    第2步

    我在回调传递样式中重写了reset块,假装不是shift,而是每个方法都有一个名为cb的第二个参数,它需要一个函数来做块的其余部分。这是重置块在此之后的样子:

      methods.method1(5, {f: Int => {
        println(f);
        methods.method2(42, {s: String => {
            println(s);
        });
      });
    

    看看发生了什么?所以现在我没有编写看似阻塞的代码,而是自己明确地分隔连续性并将它们作为函数传递给每个方法。因此,对于这些情况中的每一种情况,很明显我的每个匿名回调函数都不需要返回任何内容,事实上,它们都应该返回Unit,否则它们将污染它们的方法的返回类型正在被传入。我认为这就是编译器试图告诉我的内容(尽管我可能错了)。

    这里的method1对于我的回调式程序来说应该是什么样子

       def method1(param:Int, cb:(Float=>Unit)):Unit = {
         cb(param.toFloat);
       }
    

    method2类似,但需要(String=>Unit)。现在很明显我的方法也应该返回Unit,否则它们可能会污染回调函数的返回类型。

    第2步结论

    我认为我的很多困惑源于这样一个事实:由于某种原因,我头脑中的图片是每个shift只捕获到下一个shift作为延续。当然,情况并非如此;每个shift必须捕获reset块的整个剩余,包括以下所有shift s,以便它形成一个大的嵌套回调函数情况。此外,所有回调和所有CPS调用的方法应该总是(据我所知)返回Unit,因为它们的结果不仅不会做任何事情,而且会污染函数的返回类型调用它们,依此类推回调链。

    第3步

    现在我查看了shift的签名。它就在我面前:

    def shift[A,B,C](fun: (A => B) => C)): A @cpsParam[B,C]
    

    当我看到这个时,我意识到结合我的回调式练习,这里有足够的信息(即使没有完全理解幕后的shift做什么)把它变成基本上是一个练习在维度分析中。

    我知道method1的结果将是Float。因此,继续回调(在上面表示为(A => B))需要接受Float作为其参数。这会将A修复为Float。因此,method1现在看起来像这样:

    def method1(param:Int): Float @cpsParam[B,C] = shift { cb: (Float => B) => {
        ...
        C
      }
    }
    

    换句话说,我传递给shift的函数必须从Float到B取一个函数,然后返回C.我从练习中知道回调应该返回Unit或者事情变得混乱。我也知道在我的回调练习中,方法本身应该返回Unit,因为它们将实际结果作为参数传递给延续。这类似于C也是单位。所以这意味着method1必须如下所示:

    def method1(param:Int): Float @cpsParam[Unit,Unit] = shift { cb: (Float => Unit) => {
        cb(param);
      }
    }
    

    method2将是相同的,除了回调将采用字符串。

    我学到了什么

    现在在我看来,你可以简单地记住,如果你正在编写一个回调驱动的程序,几乎所有涉及的函数都会返回Unit,而不是被所有类型参数混淆。因为任何结果都作为参数传递而不是被返回。

    这意味着,据我所知,B中的Cshift成为其他任何目标的目的不大比Unit。这是完全合理的,因为有一个注释@suspendable,它是@cps[Unit]的快捷方式,是@cpsParam[Unit,Unit]的快捷方式。

    我不知道为什么scala-lang.org上的例子就是这样的废话。但实际上他们需要说的是,"如果你需要使用除MyReturnType @suspendable之外的任何东西,那么你可能做错了,顺便说一句shift所采用的函数参数也应该可能会返回Unit。"然后,我仍然会度过我生命中最后几个宝贵的日子。

    快乐结局

    上面提到的具有更改的程序完全编译并按顺序运行这两种方法。所以这让我相信我终于做对了。如果您是具有深入了解CPS的博士,那么请纠正我的遗嘱中的任何不准确之处。