什么是Scala延续以及为什么要使用它们?

Dav*_*ave 84 continuations scala scala-2.8 delimited-continuations

我刚刚完成了Scala编程,我一直在研究Scala 2.7和2.8之间的变化.似乎最重要的一个是continuation插件,但我不明白它对它有用或它是如何工作的.我已经看到它对异步I/O很有用,但我还是找不到原因.关于这个主题的一些更受欢迎的资源是:

关于Stack Overflow的这个问题:

不幸的是,这些引用中没有一个试图定义什么是continuation或者shift/reset函数应该做什么,而且我没有找到任何引用.我无法猜测链接文章中的任何示例是如何工作的(或者他们做了什么),因此帮助我的一种方法可能是逐行浏览其中一个示例.即使是第三篇文章中的简单文章:

reset {
    ...
    shift { k: (Int=>Int) =>  // The continuation k will be the '_ + 1' below.
        k(7)
    } + 1
}
// Result: 8
Run Code Online (Sandbox Code Playgroud)

为什么结果是8?这可能会帮助我开始.

Dan*_*ral 37

我的博客确实解释了什么resetshift做了,所以你可能想再读一遍.

我在博客中指出的另一个好消息来源是关于延续传递风格的维基百科条目.到目前为止,这个问题是最明确的,尽管它没有使用Scala语法,并且显式传递了延续.

关于分隔延续的论文,我在我的博客中链接但似乎已经破碎,提供了许多使用示例.

但我认为分隔延续概念的最好例子是Scala Swarm.在其中,库在一个点停止执行代码,剩余的计算成为延续.然后,库会执行某些操作 - 在这种情况下,将计算转移到另一个主机,并将结果(已访问的变量的值)返回到已停止的计算.

现在,您甚至不了解Scala页面上的简单示例,所以阅读我的博客.在其中我关心解释这些基础知识,结果是什么8.


Ale*_*eth 31

我发现现有的解释在解释这个概念方面不如我希望的那么有效.我希望这个清楚(并且正确.)我还没有使用延续.

cf调用continuation函数时:

  1. 执行会跳过shift块的其余部分,并在其结尾处再次开始
    • 传递给的参数cfshift当执行继续时块"评估"的内容.对于每次通话,这可能会有所不同cf
  2. 执行一直持续到reset块结束(或直到调用reset如果没有块)
    • reset块的结果(或者reset如果没有块则参数为())cf返回的结果
  3. cfshift块结束之后继续执行
  4. 执行会跳过reset块结束(或重置调用?)

因此,在此示例中,请遵循A到Z中的字母

reset {
  // A
  shift { cf: (Int=>Int) =>
    // B
    val eleven = cf(10)
    // E
    println(eleven)
    val oneHundredOne = cf(100)
    // H
    println(oneHundredOne)
    oneHundredOne
  }
  // C execution continues here with the 10 as the context
  // F execution continues here with 100
  + 1
  // D 10.+(1) has been executed - 11 is returned from cf which gets assigned to eleven
  // G 100.+(1) has been executed and 101 is returned and assigned to oneHundredOne
}
// I
Run Code Online (Sandbox Code Playgroud)

这打印:

11
101
Run Code Online (Sandbox Code Playgroud)

  • 当我试图编译它时,我有一个错误说"无法计算CPS转换函数结果的类型"..我不知道它是什么,它既不是如何解决它 (2认同)

She*_*III 9

鉴于Scala的分隔连续的研究论文中的规范示例,稍微修改,因此函数输入shift被赋予名称f,因此不再是匿名的.

def f(k: Int => Int): Int = k(k(k(7)))
reset(
  shift(f) + 1   // replace from here down with `f(k)` and move to `k`
) * 2
Run Code Online (Sandbox Code Playgroud)

Scala的插件变换本实施例中,使得所述计算(的输入参数中reset从每个开始)shift到的调用 reset替换用的功能(例如f)输入到shift.

替换的计算被移位(即移动)到函数中k.该函数f输入功能k,其中k 包含被替换的计算,k投入x: Int,并在计算k内容替换shift(f)x.

f(k) * 2
def k(x: Int): Int = x + 1
Run Code Online (Sandbox Code Playgroud)

其效果如下:

k(k(k(7))) * 2
def k(x: Int): Int = x + 1
Run Code Online (Sandbox Code Playgroud)

注意Int输入参数x的类型(即类型签名k)由输入参数的类型签名给出f.

另一个借用概念等效抽象的例子,即read函数输入shift:

def read(callback: Byte => Unit): Unit = myCallback = callback
reset {
  val byte = "byte"

  val byte1 = shift(read)   // replace from here with `read(callback)` and move to `callback`
  println(byte + "1 = " + byte1)
  val byte2 = shift(read)   // replace from here with `read(callback)` and move to `callback`
  println(byte + "2 = " + byte2)
}
Run Code Online (Sandbox Code Playgroud)

我相信这将被翻译成逻辑等同于:

val byte = "byte"

read(callback)
def callback(x: Byte): Unit {
  val byte1 = x
  println(byte + "1 = " + byte1)
  read(callback2)
  def callback2(x: Byte): Unit {
    val byte2 = x
    println(byte + "2 = " + byte1)
  }
}
Run Code Online (Sandbox Code Playgroud)

我希望这可以阐明连贯的共同抽象,这些抽象通过先前介绍这两个例子而有些混淆.例如,规范的第一个例子在研究论文中被提出作为一个匿名函数,而不是我的名字f,因此一些读者并不是立即清楚地知道它与借用的第二个例子read中的抽象类似.

因此,分隔的延续创造了一种从"你从外面打电话给我reset"到"我叫你在里面reset" 的控制倒置的错觉.

注意返回类型f是,但k不是,必须与返回类型相同reset,即只要返回相同的类型,f就可以自由地声明任何返回类型.同上和(见下文).kfresetreadcaptureENV


定界延续不会隐含地反转状态控制,例如read并且callback不是纯函数.因此,调用者不能创建引用透明的表达式,因此不具有对预期命令性语义的声明性(也称为透明)控制.

我们可以使用分隔的continuation显式地实现纯函数.

def aread(env: ENV): Tuple2[Byte,ENV] {
  def read(callback: Tuple2[Byte,ENV] => ENV): ENV = env.myCallback(callback)
  shift(read)
}
def pure(val env: ENV): ENV {
  reset {
    val (byte1, env) = aread(env)
    val env = env.println("byte1 = " + byte1)
    val (byte2, env) = aread(env)
    val env = env.println("byte2 = " + byte2)
  }
}
Run Code Online (Sandbox Code Playgroud)

我相信这将被翻译成逻辑等同于:

def read(callback: Tuple2[Byte,ENV] => ENV, env: ENV): ENV =
  env.myCallback(callback)
def pure(val env: ENV): ENV {
  read(callback,env)
  def callback(x: Tuple2[Byte,ENV]): ENV {
    val (byte1, env) = x
    val env = env.println("byte1 = " + byte1)
    read(callback2,env)
    def callback2(x: Tuple2[Byte,ENV]): ENV {
      val (byte2, env) = x
      val env = env.println("byte2 = " + byte2)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

由于环境明显,这会变得很吵.

切向注意,Scala没有Haskell的全局类型推断,因此据我所知,不能支持隐式提升到状态monad unit(作为隐藏显式环境的一种可能策略),因为Haskell的全局(Hindley-Milner)类型推断取决于不支持菱形多虚拟继承.


sta*_*lue 8

继续捕获计算的状态,稍后调用.

考虑离开shift表达式并将重置表达式保留为函数之间的计算.在shift表达式中,这个函数叫做k​​,它是continuation.您可以传递它,稍后调用它,甚至不止一次.

我认为重置表达式返回的值是=>之后的shift表达式内的表达式的值,但是对此我不太确定.

因此,通过continuation,您可以在函数中包含一个相当任意且非本地的代码片段.这可用于实现非标准控制流程,例如协同处理或回溯.

因此,应该在系统级别上使用continuation.通过你的应用程序代码喷洒它们将是噩梦的必然配方,比使用goto的最糟糕的意大利面条代码更糟糕.

免责声明:我对Scala中的延续没有深入的了解,我只是通过查看示例和了解Scheme的延续来推断它.


Dmi*_*lov 5

从我的角度来看,最好的解释是在这里:http : //jim-mcbeath.blogspot.ru/2010/08/delimited-continuations.html

示例之一:

为了更清楚地看到控制流,可以执行以下代码片段:

reset {
    println("A")
    shift { k1: (Unit=>Unit) =>
        println("B")
        k1()
        println("C")
    }
    println("D")
    shift { k2: (Unit=>Unit) =>
        println("E")
        k2()
        println("F")
    }
    println("G")
}
Run Code Online (Sandbox Code Playgroud)

这是上面的代码产生的输出:

A
B
D
E
G
F
C
Run Code Online (Sandbox Code Playgroud)