发布于 2013-04-15 15:01:21
在延续插件中实现的Scala 分隔的延续是对Danvy和Filinski引入的移位和重置控制操作符的改编。参见他们的抽象控制和表示控制: 1990和1992年CPS转换papers的研究。在类型化语言的背景下,EPFL团队的工作扩展了Asai的工作。请参阅2007年的论文here。
这应该是大量的形式主义!我扫了一眼,不幸的是,它们需要流利地使用lambda微积分符号。
另一方面,我发现了以下使用Shift和Reset tutorial的编程,当我开始将示例翻译到Scala时,以及当我读到"2.6如何提取带分隔符的延续“一节时,我感觉自己在理解上真的有了突破。
reset
操作符分隔程序的一部分。shift
用于存在值的位置(可能包括单位)。你可以把它想象成一个洞。让我们用◉来表示它。
因此,让我们来看看下面的表达式:
reset { 3 + ◉ - 1 } // (1)
reset { // (2)
val s = if (◉) "hello" else "hi"
s + " world"
}
reset { // (3)
val s = "x" + (◉: Int).toString
s.length
}
shift
所做的是将reset中的程序转换为您可以访问的函数(此过程称为具体化)。在上述情况下,函数为:
val f1 = (i: Int) => 3 + i - 1 // (1)
val f2 = (b: Boolean) => {
val s = if (b) "hello" else "hi" // (2)
s + " world"
}
val f3 = (i: Int) => { // (3)
val s = "x" + i.toString
s.length
}
该函数称为延续,并作为参数提供给它自己的参数。移位签名为:
shift[A, B, C](fun: ((A) => B) => C): A
延续将是(A => B)函数,无论谁在shift
中编写代码,都会决定对它做什么(或不做什么)。如果你简单地返回它,你真的能感觉到它能做什么。然后,reset
的结果就是具体化的计算本身:
val f1 = reset { 3 + shift{ (k:Int=>Int) => k } - 1 }
val f2 = reset {
val s =
if (shift{(k:Boolean=>String) => k}) "hello"
else "hi"
s + " world"
}
val f3 = reset {
val s = "x" + (shift{ (k:Int=>Int) => k}).toString
s.length
}
我认为物化方面确实是理解Scala分隔延续的一个重要方面。
从类型的角度来看,如果函数k
的类型为(A=>B),则shift
的类型为A@cpsParam[B,C]
。类型C
完全由您在shift
中选择返回的内容决定。返回带有cpsParam
或cps
注释的类型的表达式在EPFL论文中被限定为impure。这与没有cps注释类型的纯表达式相反。
不纯计算被转换为Shift[A, B, C]
对象(现在在标准库中称为ControlContext[A, B, C]
)。Shift
对象正在扩展延续单元格。它们的正式实现在EPFL第4页3.1节中。map
方法将纯计算与Shift
对象相结合。flatMap
方法将不纯计算与Shift
对象结合在一起。
延续插件按照EPLF paper的3.4节中给出的大纲执行代码转换。基本上,shift
被转换为Shift
对象。之后出现的纯表达式与映射组合,不纯表达式与flatMaps组合(参见图4的更多规则)。最后,一旦所有内容都转换为封闭的重置,如果所有内容都进行了类型检查,那么在所有的map和flatMaps之后,最终的Shift对象的类型应该是Shift[A, A, C]
。reset
函数实现了所包含的标识,并使用Shift
函数作为参数调用该函数。
总之,我认为EPLF论文确实包含了对发生的事情的正式描述( 3.1和3.4节以及图4)。我提到的教程有非常好的例子,这些例子给人一种很好的分界延续的感觉。
发布于 2012-01-13 23:21:19
引用wikipedia
定界连续,可组合连续或部分连续,是已被具体化为函数的连续帧的“切片”。
这方面的Scala语法是:
// Assuming g: X => anything
reset {
A
g(shift { (f: (X) => Y) => /* code using function f */ })
B
}
上面的连续帧是在shift
之后执行的所有内容,直到由reset
分隔的块的末尾。这包括调用函数g
,因为它只有在计算完shift
以及B
中的所有代码之后才会被调用。
函数g
不是必需的--您可以改为调用一个方法,或者完全忽略shift
的结果。我展示它只是为了说明shift
调用返回了一个可以使用的值。
换句话说,继续帧变成以下函数:
// Assuming g: X => anything
def f: (X) => Y = { x =>
g(x)
B
}
整个重置主体变成这样:
// Assuming g: X => anything
A
def f: (X) => Y = { x =>
g(x)
B
}
/* code using function f */
请注意,B
中的最后一条语句必须具有Y
类型。计算的结果是shift
块的内容的结果,就像上面的转换一样。
如果你想要更高的精确度,可以使用check the paper来描述Scala中的定界延续。确切的类型可以在API documentation上找到。
发布于 2018-09-23 22:29:08
延续允许你在移位后(但在重置中)捕获代码,并像这样应用它:
import scala.util.continuations._
def main(args: Array[String]): Unit = {
reset {
shift { continue: (Int => Int) =>
val result: Int = continue(continue(continue(7)))
println("result: " + result) // result: 10
} + 1
}
}
在这种情况下,shift外部(但在reset内部)的代码是+1,所以每次调用continue时,都会应用{ _+1 }。因此,continue(continue(continue(7)))
的结果是7+1+1+ 1,即10。
下面是取自here的更多示例代码
import scala.util.continuations._
import java.util.{Timer,TimerTask}
def main(args: Array[String]): Unit = {
val timer = new Timer()
type ContinuationInputType = Unit
def sleep(delay: Int) = shift { continue: (ContinuationInputType => Unit) =>
timer.schedule(new TimerTask {
val nothing: ContinuationInputType = ()
def run() = continue(nothing) // in a real program, we'd execute our continuation on a thread pool
}, delay)
}
reset {
println("look, Ma ...")
sleep(1000)
println(" no threads!")
}
}
在上面的代码中,在移位之后但在重置内部的代码是println(" no threads!")
。因此,如果我们替换这个:
def run() = continue(nothing)
有了这个:
def run() = continue(continue(continue(nothing)))
我们得到以下输出:
look, Ma ...
no threads!
no threads!
no threads!
而不是下面的输出:
look, Ma ...
no threads!
因此,我们在此更改后的代码基本上等同于:
import java.util.{Timer,TimerTask}
def main(args: Array[String]): Unit = {
println("look, Ma ...")
timer.schedule(new TimerTask {
def run() = {
println(" no threads!")
println(" no threads!")
println(" no threads!")
}
}, 1000)
}
您可以尝试使用代码here。
请注意,在调用continue之前的所有代码都只执行一次,而shift结束和reset结束之间的所有代码执行的次数与调用continue的次数相同。如果我们的continuation从未被调用过,那么位于shift结束和reset结束之间的代码就永远不会被执行。因此,Scala中的延续是一个lambda,它捕获一个移位结束和该移位的封闭重置结束之间的所有代码。
还要注意,如果在线程池上执行延续,则其余代码( shift结束和重置结束之间的所有代码)将在该线程池提供给我们的线程上执行。因此,如果我们的continuation在线程池线程#1上运行,那么println(" no threads!")
将在线程池线程#1上运行,但是println("look, Ma ...")
将在主线程上运行。正因为如此,延续特性可用于在异步I/O之上实现外观,使其看起来像阻塞IO。
https://stackoverflow.com/questions/8849185
复制相似问题