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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1512930/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 01:40:45  来源:igfitidea点击:

What are Scala continuations and why use them?

scalascala-2.8continuationsdelimited-continuations

提问by Dave

I just finished Programming in Scala, and I've been looking into the changes between Scala 2.7 and 2.8. The one that seems to be the most important is the continuations plugin, but I don't understand what it's useful for or how it works. I've seen that it's good for asynchronous I/O, but I haven't been able to find out why. Some of the more popular resources on the subject are these:

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

And this question on Stack Overflow:

Stack Overflow 上的这个问题:

Unfortunately, none of these references try to define what continuations are for or what the shift/reset functions are supposed to do, and I haven't found any references that do. I haven't been able to guess how any of the examples in the linked articles work (or what they do), so one way to help me out could be to go line-by-line through one of those samples. Even this simple one from the third article:

不幸的是,这些参考文献都没有试图定义延续的用途或移位/重置函数应该做什么,而且我还没有找到任何可以做到的参考文献。我无法猜测链接文章中的任何示例是如何工作的(或它们的作用),因此帮助我的一种方法可能是逐行浏览其中一个示例。即使是第三篇文章中的这个简单的:

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

Why is the result 8? That would probably help me to get started.

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

采纳答案by Daniel C. Sobral

My blogdoes explain what resetand shiftdo, so you may want to read that again.

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

Another good source, which I also point in my blog, is the Wikipedia entry on continuation passing style. That one is, by far, the most clear on the subject, though it does not use Scala syntax, and the continuation is explicitly passed.

另一个很好的来源,我也在我的博客中指出,是关于延续传递风格的维基百科条目。到目前为止,该主题最清楚,尽管它不使用 Scala 语法,并且显式传递了延续。

The paper on delimited continuations, which I link to in my blog but seems to have become broken, gives many examples of usage.

我在博客中链接到的有关分隔延续的论文,但似乎已损坏,提供了许多用法示例。

But I think the best example of the conceptof delimited continuations is Scala Swarm. In it, the library stopsthe execution of your code at one point, and the remaining computation becomes the continuation. The library then does something -- in this case, transferring the computation to another host, and returns the result (the value of the variable which was accessed) to the computation that was stopped.

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

Now, you don't understand even the simple example on the Scala page, so doread my blog. In it I'm onlyconcerned with explaining these basics, of why the result is 8.

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

回答by Alex Neth

I found the existing explanations to be less effective at explaining the concept than I would hope. I hope this one is clear (and correct.) I have not used continuations yet.

我发现现有的解释在解释这个概念方面没有我希望的那么有效。我希望这个是清楚的(和正确的)。我还没有使用延续。

When a continuation function cfis called:

cf调用延续函数时:

  1. Execution skips over the rest of the shiftblock and begins again at the end of it
    • the parameter passed to cfis what the shiftblock "evaluates" to as execution continues. this can be different for every call to cf
  2. Execution continues until the end of the resetblock (or until a call to resetif there is no block)
    • the result of the resetblock (or the parameter to reset() if there is no block) is what cfreturns
  3. Execution continues after cfuntil the end of the shiftblock
  4. Execution skips until the end of the resetblock (or a call to reset?)
  1. 执行跳过shift块的其余部分并在它的末尾再次开始
    • 传递给的参数cfshift块在继续执行时“评估”的内容。这对于每次调用都可能不同cf
  2. 执行一直持续到reset块结束(reset如果没有块,则调用直到)
    • reset块的结果reset(如果没有块,则为 ()的参数)是cf返回的结果
  3. 继续执行cf直到shift块结束
  4. 执行跳过直到reset块结束(或调用重置?)

So in this example, follow the letters from A to Z

所以在这个例子中,按照从 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

This prints:

这打印:

11
101

回答by Shelby Moore III

Given the canonical example from the research paperfor Scala's delimited continuations, modified slightly so the function input to shiftis given the name fand thus is no longer anonymous.

鉴于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

The Scala plugin transforms this example such that the computation (within the input argument of reset) starting from each shiftto the invocation of resetis replacedwith the function (e.g. f) input to shift.

Scala 插件转换了这个例子,使得reset从 eachshift到调用 的计算(在 的输入参数内)reset替换为函数(例如f)输入到shift

The replaced computation is shifted(i.e. moved) into a function k. The function finputs the function k, where kcontainsthe replaced computation, kinputs x: Int, and the computation in kreplaces shift(f)with x.

被替换的计算被转移(即移动)到一个函数中k。函数f输入函数k,其中k包含替换的计算,k输入x: Int,并且计算中的k替换shift(f)x

f(k) * 2
def k(x: Int): Int = x + 1

Which has the same effect as:

这与以下效果相同:

k(k(k(7))) * 2
def k(x: Int): Int = x + 1

Note the type Intof the input parameter x(i.e. the type signature of k) was given by the type signature of the input parameter of f.

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

Another borrowedexample with the conceptually equivalent abstraction, i.e. readis the function input to shift:

另一个概念上等效抽象的借用示例,即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)
}

I believe this would be translated to the logical equivalent of:

我相信这将被转化为逻辑等价物:

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)
  }
}

I hope this elucidates the coherent common abstraction which was somewhat obfuscated by prior presentation of these two examples. For example, the canonical first example was presented in the research paperas an anonymous function, instead of my named f, thus it was not immediately clear to some readers that it was abstractly analogous to the readin the borrowedsecond example.

我希望这阐明了连贯的共同抽象,这在这两个示例的先前介绍中有些模糊。例如,规范的第一个例子在研究论文中作为匿名函数呈现,而不是我的命名函数,f因此一些读者并没有立即清楚它与借用的第二个例子read中的抽象相似。

Thus delimited continuations create the illusion of an inversion-of-control from "you call me from outside of reset" to "I call you inside reset".

因此,分隔的延续创造了一种控制反转的错觉,从“你从外面叫我reset”到“我在里面叫你reset”。

Note the return type of fis, but kis not, required to be the same as the return type of reset, i.e. fhas the freedom to declare any return type for kas long as freturns the same type as reset. Ditto for readand capture(see also ENVbelow).

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



Delimited continuations do not implicitly invert the control of state, e.g. readand callbackare not pure functions. Thus the caller can not create referentially transparent expressions and thus does not have declarative (a.k.a. transparent) control over intended imperative semantics.

定界的延续并不隐含地反转状态的控制,例如readcallback并且不是纯函数。因此,调用者无法创建引用透明的表达式,因此无法对预期的命令式语义进行声明性(又名透明)控制

We can explicitly achieve pure functions with delimited continuations.

我们可以显式地实现带有分隔延续的纯函数。

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)
  }
}

I believe this would be translated to the logical equivalent of:

我相信这将被转化为逻辑等价物:

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)
    }
  }
}

This is getting noisy, because of the explicit environment.

由于明确的环境,这变得越来越嘈杂。

Tangentially note, Scala does not have Haskell's global type inference and thus as far as I know couldn't support implicit lifting to a state monad's unit(as one possible strategy for hiding the explicit environment), because Haskell's global (Hindley-Milner) type inference depends on not supporting diamond multiple virtual inheritance.

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

回答by starblue

Continuation capture the state of a computation, to be invoked later.

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

Think of the computation between leaving the shift expression and leaving the reset expression as a function. Inside the shift expression this function is called k, it is the continuation. You can pass it around, invoke it later, even more than once.

想想离开 shift 表达式和离开 reset 表达式作为函数之间的计算。在移位表达式中,此函数称为 k,它是延续。您可以传递它,稍后调用它,甚至不止一次。

I think the value returned by the reset expression is the value of the expression inside the shift expression after the =>, but about this I'm not quite sure.

我认为 reset 表达式返回的值是 => 之后的 shift 表达式中表达式的值,但对此我不太确定。

So with continuations you can wrap up a rather arbitrary and non-local piece of code in a function. This can be used to implement non-standard control flow, such as coroutining or backtracking.

因此,通过延续,您可以在函数中包含一段相当随意且非本地的代码。这可用于实现非标准控制流,例如协程或回溯。

So continuations should be used on a system level. Sprinkling them through your application code would be a sure recipe for nightmares, much worse than the worst spaghetti code using goto could ever be.

因此,应该在系统级别使用延续。将它们散布在您的应用程序代码中肯定会导致噩梦,这比使用 goto 的最糟糕的意大利面条式代码要糟糕得多。

Disclaimer:I have no in depth understanding of continuations in Scala, I just inferred it from looking at the examples and knowing continuations from Scheme.

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

回答by Dmitry Bespalov

From my point of view, the best explanation was given here: http://jim-mcbeath.blogspot.ru/2010/08/delimited-continuations.html

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

One of examples:

示例之一:

To see the control flow a little more clearly, you can execute this code snippet:

要更清楚地查看控制流,您可以执行以下代码片段:

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")
}

Here's the output the above code produces:

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

A
B
D
E
G
F
C

回答by VonC

Another (more recent -- May 2016) article on Scala continuations is:
"Time Travel in Scala: CPS in Scala (scala's continuation)" by Shivansh Srivastava (shiv4nsh).
It also refers to Jim McBeath's articlementioned in Dmitry Bespalov's answer.

另一篇(最近 - 2016 年 5 月)关于 Scala 延续的文章是:
Scala 中的时间旅行:Scala 中的 CPS(Scala 的延续)”,作者是 Shivansh Srivastava ( shiv4nsh)
它还参考了Dmitry Bespalov回答中提到的Jim McBeath文章

But before that, it describes Continuations like so:

但在此之前,它是这样描述 Continuations 的:

A continuation is an abstract representation of the control state of a computer program.
So what it actually means is that it is a data structure that represents the computational process at a given point in the process's execution; the created data structure can be accessed by the programming language, instead of being hidden in the runtime environment.

To explain it further we can have one of the most classic example,

Say you're in the kitchen in front of the refrigerator, thinking about a sandwich. You take a continuation right there and stick it in your pocket.
Then you get some turkey and bread out of the refrigerator and make yourself a sandwich, which is now sitting on the counter.
You invoke the continuation in your pocket, and you find yourself standing in front of the refrigerator again, thinking about a sandwich. But fortunately, there's a sandwich on the counter, and all the materials used to make it are gone. So you eat it. :-)

In this description, the sandwichis part of the program data(e.g., an object on the heap), and rather than calling a “make sandwich” routine and then returning, the person called a “make sandwich with current continuation” routine, which creates the sandwich and then continues where execution left off.

延续是计算机程序控制状态的抽象表示
所以它的实际意思是,它是一种数据结构,表示在进程执行中给定点的计算过程;创建的数据结构可以被编程语言访问,而不是隐藏在运行时环境中。

为了进一步解释它,我们可以举一个最经典的例子,

假设您在冰箱前的厨房里想吃三明治。你在那里接续,然后把它塞进你的口袋。
然后你从冰箱里拿出一些火鸡和面包,给自己做一个三明治,现在放在柜台上。
你调用口袋里的延续,你发现自己又站在冰箱前,想着一个三明治。不过好在柜台上有一个三明治,制作它的所有材料都没有了。所以你吃它。:-)

在这个描述中,sandwich程序数据的一部分(例如,堆上的对象),而不是调用“ make sandwich”例程然后返回,而是调用“ make sandwich with current continuation”例程,它创建三明治然后继续执行离开了。

That being said, as announced in April 2014 for Scala 2.11.0-RC1

话虽如此,正如20144 月宣布的 Scala 2.11.0-RC1

We are looking for maintainers to take over the following modules: scala-swing, scala-continuations.
2.12 will not include them if no new maintainer is found.
We will likely keep maintaining the other modules (scala-xml, scala-parser-combinators), but help is still greatly appreciated.

我们正在寻找维护者来接管以下模块:scala-swingscala-continuations
如果没有找到新的维护者,2.12 将不包括它们
我们可能会继续维护其他模块(scala-xml、scala-parser-combinator),但仍然非常感谢帮助。

回答by 18446744073709551615

Scala Continuations via Meaningful Examples

Scala Continuations 通过有意义的例子

Let us define from0to10that expresses the idea of iteration from 0 to 10:

让我们定义from0to10表达从 0 到 10 迭代的想法:

def from0to10() = shift { (cont: Int => Unit) =>
   for ( i <- 0 to 10 ) {
     cont(i)
   }
}

Now,

现在,

reset {
  val x = from0to10()
  print(s"$x ")
}
println()

prints:

印刷:

0 1 2 3 4 5 6 7 8 9 10 

In fact, we do not need x:

事实上,我们不需要x

reset {
  print(s"${from0to10()} ")
}
println()

prints the same result.

打印相同的结果。

And

reset {
  print(s"(${from0to10()},${from0to10()}) ")
}
println()

prints all pairs:

打印所有对:

(0,0) (0,1) (0,2) (0,3) (0,4) (0,5) (0,6) (0,7) (0,8) (0,9) (0,10) (1,0) (1,1) (1,2) (1,3) (1,4) (1,5) (1,6) (1,7) (1,8) (1,9) (1,10) (2,0) (2,1) (2,2) (2,3) (2,4) (2,5) (2,6) (2,7) (2,8) (2,9) (2,10) (3,0) (3,1) (3,2) (3,3) (3,4) (3,5) (3,6) (3,7) (3,8) (3,9) (3,10) (4,0) (4,1) (4,2) (4,3) (4,4) (4,5) (4,6) (4,7) (4,8) (4,9) (4,10) (5,0) (5,1) (5,2) (5,3) (5,4) (5,5) (5,6) (5,7) (5,8) (5,9) (5,10) (6,0) (6,1) (6,2) (6,3) (6,4) (6,5) (6,6) (6,7) (6,8) (6,9) (6,10) (7,0) (7,1) (7,2) (7,3) (7,4) (7,5) (7,6) (7,7) (7,8) (7,9) (7,10) (8,0) (8,1) (8,2) (8,3) (8,4) (8,5) (8,6) (8,7) (8,8) (8,9) (8,10) (9,0) (9,1) (9,2) (9,3) (9,4) (9,5) (9,6) (9,7) (9,8) (9,9) (9,10) (10,0) (10,1) (10,2) (10,3) (10,4) (10,5) (10,6) (10,7) (10,8) (10,9) (10,10) 

Now, how does that work?

现在,它是如何工作的?

There is the called code, from0to10, and the calling code. In this case, it is the block that follows reset. One of the parameters passed to the called code is a return address that shows what part of the calling code has not yet been executed (**). That part of the calling code is the continuation. The called code can do with that parameter whatever it decides to: pass control to it, or ignore, or call it multiple times. Here from0to10calls that continuation for each integer in the range 0..10.

还有就是所谓的代码from0to10以及调用代码。在这种情况下,它是后面的块reset。传递给被调用代码的参数之一是返回地址,显示调用代码的哪一部分尚未执行 (**)。调用代码的那部分是continuation。被调用的代码可以使用该参数做任何决定:将控制权传递给它,或者忽略它,或者多次调用它。Herefrom0to10为 0..10 范围内的每个整数调用该延续。

def from0to10() = shift { (cont: Int => Unit) =>
   for ( i <- 0 to 10 ) {
     cont(i) // call the continuation
   }
}

But where does the continuation end? This is important because the last returnfrom the continuation returns control to the called code, from0to10. In Scala, it ends where the resetblock ends (*).

但是延续到哪里结束呢?这很重要,因为return延续中的最后一个将控制权返回给被调用的代码from0to10。在 Scala 中,它在reset块结束的地方 (*) 结束。

Now, we see that the continuation is declared as cont: Int => Unit. Why? We invoke from0to10as val x = from0to10(), and Intis the type of value that goes to x. Unitmeans that the block after resetmust return no value (otherwise there will be a type error). In general, there are 4 type signatures: function input, continuation input, continuation result, function result. All four must match the invocation context.

现在,我们看到延续被声明为cont: Int => Unit。为什么?我们调用from0to10as val x = from0to10(),并且Int是转到 的值的类型xUnit意味着后面的块reset必须没有返回值(否则会出现类型错误)。一般来说,有4种类型签名:函数输入、延续输入、延续结果、函数结果。所有四个都必须匹配调用上下文。

Above, we printed pairs of values. Let us print the multiplication table. But how do we output \nafter each row?

上面,我们打印了成对的值。让我们打印乘法表。但是我们如何\n在每一行之后输出呢?

The function backlets us specify what must be done when control returns back, from the continuation to the code that called it.

该函数back让我们指定当控制返回时必须做什么,从延续到调用它的代码。

def back(action: => Unit) = shift { (cont: Unit => Unit) =>
  cont()
  action
}

backfirst calls its continuation, and then performs the action.

back首先调用它的延续,然后执行操作

reset {
  val i = from0to10()
  back { println() }
  val j = from0to10
  print(f"${i*j}%4d ") // printf-like formatted i*j
}

It prints:

它打印:

   0    0    0    0    0    0    0    0    0    0    0 
   0    1    2    3    4    5    6    7    8    9   10 
   0    2    4    6    8   10   12   14   16   18   20 
   0    3    6    9   12   15   18   21   24   27   30 
   0    4    8   12   16   20   24   28   32   36   40 
   0    5   10   15   20   25   30   35   40   45   50 
   0    6   12   18   24   30   36   42   48   54   60 
   0    7   14   21   28   35   42   49   56   63   70 
   0    8   16   24   32   40   48   56   64   72   80 
   0    9   18   27   36   45   54   63   72   81   90 
   0   10   20   30   40   50   60   70   80   90  100 

Well, now it's time for some brain-twisters. There are two invocations of from0to10. What is the continuation for the first from0to10? It follows the invocation of from0to10in the binary code, but in the source code it also includes the assignment statement val i =. It ends where the resetblock ends, but the end of the resetblock does not return control to the first from0to10. The end of the resetblock returns control to the 2nd from0to10, that in turn eventually returns control to back, and it is backthat returns control to the first invocation of from0to10. When the first (yes! 1st!) from0to10exits, the whole resetblock is exited.

好吧,现在是一些脑筋急转弯的时候了。有两次调用from0to10。第一个的延续是什么from0to10?它遵循的调用from0to10中的二进制代码,但在源代码中还包含了赋值语句val i =。它在reset块结束的地方结束,但块的结尾reset不会将控制权返回给第一个from0to10reset块的末尾将控制权返回给 2nd from0to10,后者最终将控制权返回给back,并且back将控制权返回给 的第一次调用from0to10。当第一个(是的!第一个!)from0to10退出时,整个reset块都会退出。

Such method of returning control back is called backtracking, it is a very old technique, known at least from the times of Prolog and AI-oriented Lisp derivatives.

这种返回控制权的方法称为回溯,这是一种非常古老的技术,至少从 Prolog 和面向 AI 的 Lisp 派生类时代就知道了。

The names resetand shiftare misnomers. These names should better have been left for the bitwise operations. resetdefines continuation boundaries, and shifttakes a continuation from the call stack.

名字resetshift用词不当。这些名称最好留给按位运算。reset定义延续边界,并shift从调用堆栈中获取延续。

Note(s)

笔记)

(*) In Scala, the continuation ends where the resetblock ends.Another possible approach would be to let it end where the function ends.

(*)在 Scala 中,延续在reset块结束的地方结束。另一种可能的方法是让它在函数结束的地方结束。

(**) One of the parameters of the called code is a return address that shows what part of the calling code has not yet been executed.Well, in Scala, a sequence of return addresses is used for that. How many? All of the return addresses placed on the call stack since entering the resetblock.

(**)被调用代码的参数之一是返回地址,显示调用代码的哪一部分尚未执行。好吧,在 Scala 中,为此使用了一系列返回地址。多少?自进入reset块以来放置在调用堆栈上的所有返回地址。



UPD Part 2Discarding Continuations: Filtering

UPD第 2 部分丢弃延续:过滤

def onEven(x:Int) = shift { (cont: Unit => Unit) =>
  if ((x&1)==0) {
    cont() // call continuation only for even numbers
  }
}
reset {
  back { println() }
  val x = from0to10()
  onEven(x)
  print(s"$x ")
}

This prints:

这打印:

0 2 4 6 8 10 

Let us factor out two important operations: discarding the continuation (fail()) and passing control on to it (succ()):

让我们分解出两个重要的操作:丢弃延续 ( fail()) 并将控制权交给它 ( succ()):

// fail: just discard the continuation, force control to return back
def fail() = shift { (cont: Unit => Unit) => }
// succ: does nothing (well, passes control to the continuation), but has a funny signature
def succ():Unit @cpsParam[Unit,Unit] = { }
// def succ() = shift { (cont: Unit => Unit) => cont() }

Both versions of succ()(above) work. It turns out that shifthas a funny signature, and although succ()does nothing, it must have that signature for type balance.

succ()(以上)的两个版本都有效。事实证明,它shift有一个有趣的签名,尽管succ()什么都不做,但它必须具有该签名才能进行类型平衡。

reset {
  back { println() }
  val x = from0to10()
  if ((x&1)==0) {
    succ()
  } else {
    fail()
  }
  print(s"$x ")
}

as expected, it prints

正如预期的那样,它打印

0 2 4 6 8 10

Within a function, succ()is not necessary:

在函数内,succ()不是必需的:

def onTrue(b:Boolean) = {
  if(!b) {
    fail()
  }
}
reset {
  back { println() }
  val x = from0to10()
  onTrue ((x&1)==0)
  print(s"$x ")
}

again, it prints

再次,它打印

0 2 4 6 8 10

Now, let us define onOdd()via onEven():

现在,让我们定义onOdd()通过onEven()

// negation: the hard way
class ControlTransferException extends Exception {}
def onOdd(x:Int) = shift { (cont: Unit => Unit) =>
  try {
    reset {
      onEven(x)
      throw new ControlTransferException() // return is not allowed here
    }
    cont()
  } catch {
    case e: ControlTransferException =>
    case t: Throwable => throw t
  }
}
reset {
  back { println() }
  val x = from0to10()
  onOdd(x)
  print(s"$x ")
}

Above, if xis even, an exception is thrown and the continuation is not called; if xis odd, the exception is not thrown and the continuation is called. The above code prints:

上面,如果x是偶数,则抛出异常,不调用continuation;如果x是奇数,则不会抛出异常并调用延续。上面的代码打印:

1 3 5 7 9