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

Jer*_*emy 6 continuations scala 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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句,StackOverflow没有强调scala是犯罪行为! (我的立场得到了纠正;它实际上做得很好,但不是在实时预览中)

我的问题如下:

  1. 从上述计划中的评论来看,我对scala的CPS的理解缺少什么?是否有过一个情况下,你不希望UnitB@cpsParam[B,C]
  2. 上面的程序编译和工作(打印出来"5.0").但是,我现在遇到的问题是导致我混淆的问题是当我更改reset块以尝试调用method2之后method1:

(显然你不能在列表后面有一个代码块)

reset {
  val f = methods.method1(5);
  println(f);
  val s = methods.method2(42);
  println(s);
}
Run Code Online (Sandbox Code Playgroud)

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

illegal answer type modification: scala.util.continuations.cpsParam[Unit,Float] andThen scala.util.continuations.cpsParam[Unit,String]
Run Code Online (Sandbox Code Playgroud)

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

我开始觉得你不需要成为一名博士后学生才能理解斯卡拉的CPS.但我当然还没到那里.

Jer*_*emy 4

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

我做了三件事帮助我理解了这个问题,我认为任何在 scala 的延续上遇到问题的人最好遵循以下步骤:

  1. 阅读有关 scala 延续的原始学术论文。它非常枯燥,我怀疑这主要是一群疯子的胡言乱语,但它也非常有帮助,因为它让您深入了解编译器如何转换连续驱动的代码以及它所涉及的类型和纯度问题。这样做的面孔。
  2. 以回调传递风格重写代码。 这是您可以做的最重要的事情,可以真正掌握延续流及其类型的情况。
  3. 检查,我的意思是真正检查类型签名shift并注意它在做什么。这会让你得到我所经历的顿悟。

就我而言,我输入的@cpsParams 和cb参数都是shift错误的。我将解释我如何找出我做错了什么,以便任何像我一样愚蠢的人都可以遵循相同的步骤,并希望在延续编译器让他们发疯时获得一些见解

步骤1

我读了上面的论文。大约有十几次。我对它的了解仍然很少。但我所理解的内容非常有帮助。

第2步

我以回调传递方式重写了我的块,假装每个方法都有一个第二个参数,reset而不是 a ,该参数将使用一个函数来完成块的其余部分。以下是重置块之后的样子:shiftcb

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

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

这是method1我的回调式程序的样子

   def method1(param:Int, cb:(Float=>Unit)):Unit = {
     cb(param.toFloat);
   }
Run Code Online (Sandbox Code Playgroud)

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

步骤 2 结论

我认为我的很多困惑源于这样一个事实:出于某种原因,我脑海中的画面是每一个画面shift都只捕捉到下一个画面shift作为延续。当然,事实并非如此;每个都shift必须捕获块的整个其余部分reset,包括所有以下shift内容,以便形成一个大的嵌套回调中回调的情况。此外,所有回调和所有 CPS 调用的方法都应该始终(据我所知) return Unit,因为它们的结果不仅不会做任何事情,而且可能会污染调用它们的函数的返回类型,所以在回调链上。

步骤3

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

def shift[A,B,C](fun: (A => B) => C)): A @cpsParam[B,C]
Run Code Online (Sandbox Code Playgroud)

当我看到这个时,我意识到结合我的回调式练习,这里有足够的信息(即使没有完全理解shift幕后的作用)将其基本上变成维度分析的练习。

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

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

换句话说,我传递给的函数shift必须采用从 Float 到 B 的函数,并返回 C。从我的练习中我知道回调应该返回 Unit,否则事情会变得混乱。我还知道,在我的回调练习中,方法本身显然应该返回Unit,因为它们将实际结果作为参数传递给延续。这类似于 C 也是单位。所以这意味着method1必须看起来像这样:

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

method2除了回调将采用字符串之外,其他都是相同的。

我学到的是

现在在我看来,您不必对抛出的所有类型参数感到困惑,只需记住,如果您正在编写回调驱动的程序,几乎所有涉及的函数都会返回,Unit因为任何结果都作为参数传递而不是被退回。

这意味着,据我所知,除了 之外,不会有B太多Cshift目的Unit。这是完全有道理的,因为有一个注释@suspendable是 的快捷方式,@cps[Unit]而 是 的快捷方式@cpsParam[Unit,Unit]

我不知道为什么 scala-lang.org 上的例子这么糟糕。但实际上他们需要说的是,“如果你需要使用除此之外的任何东西,MyReturnType @suspendable你可能做错了,顺便说一句,shift接受的函数参数也可能应该返回Unit。” 那我就还剩下生命中最后几天宝贵的日子了。

美好的结局

经过我上面提到的更改的程序完全可以按顺序使用这两种方法进行编译和运行。所以这让我相信我终于做对了。如果您是一位对 CPS 有深入了解的博士,那么请纠正我的漫谈中的任何不准确之处。