我只是没有得到延续!

时间:2008-09-02 20:55:13

标签: functional-programming continuations callcc

它们是什么,它们有什么用?

我没有CS学位,我的背景是VB6 - > ASP - > ASP.NET/C#。任何人都能以清晰简洁的方式解释它吗?

9 个答案:

答案 0 :(得分:41)

想象一下,如果程序中的每一行都是一个单独的函数。每个接受作为参数的下一行/要执行的函数。

使用此模型,您可以在任何行“暂停”执行并稍后继续执行。您还可以执行创造性操作,例如暂时跳过执行堆栈以检索值,或将当前执行状态保存到数据库以便稍后检索。

答案 1 :(得分:11)

你可能比你想象的更好地理解它们。

例外是“仅向上”延续的一个例子。它们允许代码内部的代码调用异常处理程序来指示问题。

Python示例:

try:
    broken_function()
except SomeException:
    # jump to here
    pass

def broken_function():
    raise SomeException() # go back up the stack
    # stuff that won't be evaluated

生成器是“向下”延续的例子。它们允许代码重新输入循环,例如,创建新值。

Python示例:

def sequence_generator(i=1):
    while True:
        yield i  # "return" this value, and come back here for the next
        i = i + 1

g = sequence_generator()
while True:
    print g.next()

在这两种情况下,这些都必须特别添加到语言中,而在具有延续的语言中,程序员可以创建这些不可用的东西。

答案 2 :(得分:9)

抬头,这个例子并不简洁,也不是特别清楚。这是对continuation的强大应用的演示。作为VB / ASP / C#程序员,您可能不熟悉系统堆栈或保存状态的概念,因此这个答案的目标是演示而不是解释。

Continuations非常通用,是一种保存执行状态并在以后恢复的方法。以下是使用Scheme中的continuation的协作多线程环境的一个小例子:

(假设操作在此处未定义的全局队列上按预期排队和出列工作)

(define (fork)
  (display "forking\n")
  (call-with-current-continuation
   (lambda (cc)
     (enqueue (lambda ()
                (cc #f)))
     (cc #t))))

(define (context-switch)
  (display "context switching\n")
  (call-with-current-continuation
   (lambda (cc)
     (enqueue
      (lambda ()
        (cc 'nothing)))
     ((dequeue)))))

(define (end-process)
  (display "ending process\n")
  (let ((proc (dequeue)))
    (if (eq? proc 'queue-empty)
        (display "all processes terminated\n")
        (proc))))

这提供了一个函数可以使用的三个动词 - fork,context-switch和end-process。 fork操作分叉线程并在一个实例中返回#t而在另一个实例中返回#f。上下文切换操作在线程之间切换,而end-process终止线程。

以下是他们使用的一个例子:

(define (test-cs)
  (display "entering test\n")
  (cond
    ((fork) (cond
              ((fork) (display "process 1\n")
                      (context-switch)
                      (display "process 1 again\n"))
              (else (display "process 2\n")
                    (end-process)
                    (display "you shouldn't see this (2)"))))
    (else (cond ((fork) (display "process 3\n")
                        (display "process 3 again\n")
                        (context-switch))
                (else (display "process 4\n")))))
  (context-switch)
  (display "ending process\n")
  (end-process)
  (display "process ended (should only see this once)\n"))

输出应为

entering test
forking
forking
process 1
context switching
forking
process 3
process 3 again
context switching
process 2
ending process
process 1 again
context switching
process 4
context switching
context switching
ending process
ending process
ending process
ending process
ending process
ending process
all processes terminated
process ended (should only see this once)

那些在课堂上学习分叉和穿线的人经常会给出类似的例子。这篇文章的目的是证明通过延续,您可以通过手动保存和恢复其状态 - 它的继续 - 在单个线程中获得类似的结果。

P.S。 - 我想在On Lisp中记得类似的东西,所以如果你想看专业代码,你应该看看这本书。

答案 3 :(得分:7)

考虑延续的一种方法是作为处理器堆栈。当你“使用current-continuation c调用”时,它调用你的函数“c”,传递给“c”的参数是你当前的堆栈,上面包含你所有的自动变量(表示为另一个函数,称之为“k”) “)。与此同时,处理器开始创建一个新的堆栈。当你调用“k”时,它会在原始堆栈上执行“从子程序返回”(RTS)指令,跳回原来的“call-with-current-continuation”(从现在起“call-cc”)的上下文on)并允许您的程序像以前一样继续。如果您将参数传递给“k”,那么这将成为“call-cc”的返回值。

从原始堆栈的角度来看,“call-cc”看起来像是普通的函数调用。从“c”的角度来看,你的原始堆栈看起来像一个永不返回的函数。

有一个关于数学家的老笑话,他抓住一只笼子里的狮子爬进笼子里,把它锁起来,宣布自己在笼子外面,而其他一切(包括狮子)都在里面。延续有点像笼子,“c”有点像数学家。你的主程序认为“c”在里面,而“c”认为你的主程序在“k”里面。

您可以使用continuation创建任意控制流结构。例如,您可以创建一个线程库。 “yield”使用“call-cc”将当前的continuation放在队列中,然后跳转到队列头部的那个。信号量也有自己的暂停连续队列,并通过将其从信号量队列中取出并将其放入主队列来重新安排线程。

答案 4 :(得分:4)

基本上,延续是一个函数停止执行的能力,然后在稍后的某个时间点回到它停止的位置。在C#中,您可以使用yield关键字执行此操作。如果你愿意,我可以详细介绍,但你想要一个简明的解释。 ; - )

答案 5 :(得分:2)

我仍然“习惯”延续,但是我觉得有用的一种思考方式就是程序计数器(PC)概念的抽象。 PC“指向”下一条在内存中执行的指令,但当然该指令(以及几乎所有指令)隐式或显式地指向后面的指令,以及任何指令应该服务中断。 (即使是NOOP指令也会隐式地对存储器中的下一条指令执行JUMP。但是如果发生中断,那通常会将JUMP包含在内存中的其他指令中。)

在某种意义上,控制可能在任何给定点跳跃的内存中程序中的每个潜在的“实时”点都是一个主动延续。可以达到的其他点是潜在的活跃延续,但更重要的是,它们是可能被“计算”(可能动态地)到达一个或多个当前活动延续的结果。

在传统的continuation介绍中,这似乎有些不合适,其中所有挂起的执行线程都明确表示为静态代码的延续;但它考虑到这样一个事实,即在通用计算机上,PC指向一个指令序列,该指令序列可能会改变代表该指令序列一部分的内存内容,从而基本上创建一个新的(或修改,如果你愿意的话) )动态继续,在创建/修改之前的继续激活中并不存在。

因此,延续可以被视为PC的高级模型,这就是为什么它在概念上包含普通过程调用/返回(就像古代铁通过低级JUMP程序调用/返回,也就是GOTO,指令加上在通话中记录PC并在返回时恢复它)以及异常,线程,协同程序等。

正如PC指向在“未来”中发生的计算一样,延续也会做同样的事情,但是在更高,更抽象的层面。 PC隐含地指内存加上所有内存位置和寄存器“绑定”到任何值,而延续通过适合语言的抽象来表示未来。

当然,虽然每台计算机(核心处理器)通常只有一台PC,但实际上有许多“活跃的”PC-ish实体,如上所述。中断向量包含一堆,堆栈更多,某些寄存器可能包含一些等等。当它们的值被加载到硬件PC时它们被“激活”,但是延续是概念的抽象,而不是PC或它们的精确等价物(虽然我们经常用这些术语来思考和编码,以保持相当简单),但没有“主”延续的固有概念。

从本质上讲,延续是“在调用时下一步该做什么”的表示,因此,可以(并且,在某些语言和延续传递式程序中,通常是)一流的像大多数其他数据类型一样被实例化,传递和丢弃的对象,就像经典计算机如何处理相对于 PC的内存位置 - 几乎可以与普通整数互换

答案 6 :(得分:2)

在C#中,您可以访问两个续点。其中一个通过return访问,允许方法从调用它的位置继续。通过throw访问的另一个方法允许方法在最近的匹配catch处继续。

有些语言允许您将这些语句视为一等值,因此您可以分配它们并在变量中传递它们。这意味着您可以隐藏returnthrow的值,并在您 准备好返回或投掷时稍后再调用它们。

Continuation callback = return;
callMeLater(callback);

这在很多情况下都很方便。一个例子就像上面的例子,你想要暂停你正在做的工作,并在事情发生后再恢复(比如获取网络请求或其他东西)。

我在我正在做的几个项目中使用它们。在一个,我正在使用它们,所以我可以在我等待网络上的IO时暂停程序,然后再恢复它。另一方面,我正在编写一种编程语言,我可以让用户访问continuation-as-values,这样他们就可以为自己编写returnthrow - 或任何其他控制流,例如{{ 1}}循环 - 没有我需要为他们做。

答案 7 :(得分:1)

想想线程。可以运行一个线程,您可以获得其计算结果。延续是一个可以复制的线程,因此您可以运行两次相同的计算。

答案 8 :(得分:1)

Continuations对Web编程有了新的兴趣,因为它们很好地反映了Web请求的暂停/恢复特性。服务器可以构建表示用户会话的连续性,并在用户继续会话时恢复。