关于函数式编程:我只是没有得到延续!

关于函数式编程:我只是没有得到延续!

I just don't get continuations!

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

我没有计算机科学学位,我的背景是 VB6 - ASP - ASP.NET/C#。谁能简明扼要地解释一下?


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

使用此模型,您可以在任何行"暂停"执行并稍后继续执行。您还可以做一些创造性的事情,比如暂时跳上执行堆栈来检索一个值,或者将当前执行状态保存到数据库中以便以后检索。


你可能比你想象的更了解它们。

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

Python 示例:

1
2
3
4
5
6
7
8
9
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 示例:

1
2
3
4
5
6
7
8
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()

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


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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(define (fork)
  (display"forking\
")
  (call-with-current-continuation
   (lambda (cc)
     (enqueue (lambda ()
                (cc #f)))
     (cc #t))))

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

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

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

以下是它们的使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(define (test-cs)
  (display"entering test\
")
  (cond
    ((fork) (cond
              ((fork) (display"process 1\
")
                      (context-switch)
                      (display"process 1 again\
"))
              (else (display"process 2\
")
                    (end-process)
                    (display"you shouldn't see this (2)"))))
    (else (cond ((fork) (display"process 3\
")
                        (display"process 3 again\
")
                        (context-switch))
                (else (display"process 4\
")))))
  (context-switch)
  (display"ending process\
")
  (end-process)
  (display"process ended (should only see this once)\
"))

输出应该是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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)

那些在类上学习过分叉和线程的人经常会得到类似的例子。这篇文章的目的是展示使用延续,您可以通过手动保存和恢复其状态 - 它的延续 - 在单个线程中实现类似的结果。

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


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

从原始堆栈的angular来看,"call-cc" 看起来像一个普通的函数调用。从 "c" 的angular来看,您的原始堆栈看起来像一个永远不会返回的函数。

有一个古老的笑话,关于一位数学家爬进笼子,锁上笼子,然后宣布自己在笼子外面,而其他所有东西(包括狮子)都在笼子里,从而在笼子里抓住了一只狮子。续行有点像笼子,"c"有点像数学家。你的主程序认为 "c" 在里面,而 "c" 认为你的主程序在 "k" 里面。

您可以使用延续创建任意的控制流结构。例如,您可以创建一个线程库。 "yield" 使用 "call-cc" 将当前延续放入队列中,然后跳转到队列头部的延续。信号量也有自己的挂起延续队列,并且通过将线程从信号量队列中取出并将其放入主队列来重新调度线程。


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


在 C# 中,您可以访问两个延续。一,通过 return 访问,让一个方法从它被调用的地方继续。另一个通过 throw 访问,让方法在最近的匹配 catch.

处继续

某些语言允许您将这些语句视为一等值,因此您可以分配它们并在变量中传递它们。这意味着您可以存储 returnthrow 的值,并在您真正准备好返回或抛出时调用它们。

1
2
Continuation callback = return;
callMeLater(callback);

这在很多情况下都很方便。一个示例与上面的示例类似,您希望暂停正在执行的工作,然后在发生某些事情(例如收到网络请求或其他事情)时恢复它。

我在我正在进行的几个项目中使用它们。一方面,我正在使用它们,这样我就可以在等待网络上的 IO 时暂停程序,然后再恢复它。另一方面,我正在编写一种编程语言,让用户可以访问作为值的延续,以便他们可以为自己编写 returnthrow - 或任何其他控制流,如 while 循环 -无需我为他们做这件事。


我仍然"习惯于"延续,但我认为有用的一种思考方式是作为程序计数器 (PC) 概念的抽象。 PC"指向"要在内存中执行的下一条指令,但是该指令(以及几乎每条指令)当然会隐式或显式地指向后面的指令,以及应该为中断提供服务的任何指令。 (即使是 NOOP 指令也会隐式跳转到内存中的下一条指令。但如果发生中断,通常会涉及跳转到内存中的其他指令。)

内存中程序中每个潜在的"活动"点,控制可能在任何给定点跳转到,从某种意义上说,是一个活动的延续。可以达到的其他点是潜在的活动延续,但更重要的是,它们是由于达到一个或多个当前活动的延续而可能被"计算"(可能是动态地)的延续。

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

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

因此,正如 PC 指出计算将发生在"未来"中,延续做同样的事情,但在更高、更抽象的层次上。 PC 隐含地指代内存加上所有内存位置并将寄存器"??绑定"到任何值,而延续则通过适合语言的抽象表示未来。

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

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


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


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


推荐阅读

    excel怎么用乘法函数

    excel怎么用乘法函数,乘法,函数,哪个,excel乘法函数怎么用?1、首先用鼠标选中要计算的单元格。2、然后选中单元格后点击左上方工具栏的fx公

    excel中乘法函数是什么?

    excel中乘法函数是什么?,乘法,函数,什么,打开表格,在C1单元格中输入“=A1*B1”乘法公式。以此类推到多个单元。1、A1*B1=C1的Excel乘法公式

    标准差excel用什么函数?

    标准差excel用什么函数?,函数,标准,什么,在数据单元格的下方输入l标准差公式函数公式“=STDEVPA(C2:C6)”。按下回车,求出标准公差值。详细

    excel常用函数都有哪些?

    excel常用函数都有哪些?,函数,哪些,常用,1、SUM函数:SUM函数的作用是求和。函数公式为=sum()例如:统计一个单元格区域:=sum(A1:A10)  统计多个

    dip医保通俗解释?dip和drg医保区别

    dip医保通俗解释?dip和drg医保区别,医保,医疗机构,本文目录dip医保通俗解释dip和drg医保区别医保dip是什么意思DIP是什么意思电子厂dip是什

    里番名词解释|里番是什么东西啊

    里番名词解释|里番是什么东西啊,动画, 里番 到底是什么?原来动画的种类这么复杂。动画是一种综合艺术,它是集合了绘画、漫画、电影、数字媒