续体

来自Local Chinese Wikipedia
(重定向自Continuation
跳转到导航 跳转到搜索

Template:NoteTA计算机科学中,续体Template:Langx,也译作计算续体、续延、延续性),是实化计算机程序控制状态的一种抽象表示。程式在运算过程中给定一个点,其“剩余将要执行的部分”,即为“续体”。借助程式语言中的续体特性,程序员不但能模组化地自订循环、break陈述句、continue陈述句等基本控制结构;此外还能以更加简练的方式实现更加复杂的控制结构或计算模式,例如:异常处理 生成器协程回溯非确定性计算概率编程[1]反向传播算法可微分编程[2]

概述[编辑]

对一段程式 1 * (2 + 3) - 4 而言,若当前正在计算 2 + 3,这部分则称为可约表达式(Template:Langx[3];而其余的部分 1 * <math>[\cdot]</math> - 4 即是续体。这里的符号 <math>[\cdot]</math> 被称为“洞”,表示可约表达式之计算结果将要回填的地方,可视为占位符[4]

续体的种类[编辑]

续体的概念在任何程式语言中都会出现,而不同的程式语言之间的差别在于是否提供构造或是算子,以供显示地捕捉跟恢复续体。不同种类的算子,亦有相异的抽象程度与表达能力。

头等续体[编辑]

若能将捕捉到的续体实化作为一般的值储存与传递,便会称其为头等续体(Template:Langx)。而当被捕捉的续体被呼叫时,程式的流程控制将会恢复到续体被捕捉时所在的地方。这赋予程式语言强大的执行顺序控制能力。它可以跨越多个呼叫堆叠来返回结果,或者跳转到此前已经退出了的函数。可以认为其保存了程序的控制流程上下文,而需要注意的是这与Template:Le不同,它不保存程序数据,只保存控制流程上下文。这经常采用“续体三明治”譬喻来说明:

你正站在冰箱前,想要做一个三明治。
你就把这个情况变成一个续体,放入口袋里。
接着,从冰箱里拿出火鸡肉和面包,并坐到桌案前,做了一个三明治。
你启用了口袋里的续体
这时发现自己再次站到了冰箱之前,想要一个三明治。
幸运的是,在桌案上就有一个三明治,而用于做三明治的材料都没有了。你可以吃三明治了。[5]

在这个譬喻中,三明治是一部分程序数据,比如在分配堆上一个对象,并非去调用“制做三明治”例程并等它返回,这里调用“以当前续体制做三明治”例程,它创建一个三明治并在已脱离执行的续体所保存的地方继续执行。

多发及单发恢复[编辑]

能够恢复多次的多发续体(Template:Langx),其表达能力比仅能恢复一次的单发续体(Template:Langx)还要强。单发续体能够直接表达循环异常处理迭代器生成器协程等控制结构,而多发续体不仅仅是具备单发续体的能力,此外还能用在非确定性回溯法布林可满足性问题反向传播这些单发续体无法直接表达的问题。 [6] [7]

然而,多发恢复的续体在编译器执行期系统的实现面临着工程上的挑战。为了实现多发恢复的语意,通常需要以复制呼叫堆叠实现,这也为多发续延带来了一些性能负担。考虑到这个原因,部分程式语言仅提供单发恢复,例如OCaml的Effect[8];也有程式语言实现提供了单发化版本的控制算子,例如Chez Scheme提供了call/1cc[9]而如何更高效地实现多发恢复,在近年来(2025)仍然是学者积极探讨的问题。[10][11][12]

学界也探讨了单发有界续体与协程的关系。有堆叠的非对称式协程,例如Lua所提供的协程,可以对应到单发有界续体。[13][14][15][16]

历史[编辑]

Gerald J. SussmanGuy L. Steele Jr.在1976年论文《Lambda:终极指令式》中,提及了Template:Le在1972年介入Template:Le变换的论文《高阶编程语言的定义性解释器》中采用了术语“续体”[17],并认定Alonzo Church在1941年著作《Lambda转换的演算》里[18],已经清晰的理解了“续体”的使用,这是用纯λ演算彻底完成所有事情即邱奇编码的唯一方式,并举出其中有序对定义所对应的Scheme表示为例[19]

(define (cons m n)
  (lambda (a) (a m n)))
(define (car a)
  (a (lambda (b c) b)))
(define (cdr a)
  (a (lambda (b c) c)))

Template:Le在1993年的论文《续体的发现》中给出了发现续体的完整历史[20]。Reynolds认为续体的最早描述由Adriaan van Wijngaarden在1964年9月作出。van Wijngaarden在奥地利维也纳附近巴登召开的关于“形式语言描述语言”的Template:Le工作会议上,在关于ALGOL 60预处理器Template:Le的论文《语法和语义的递归定义》中[21],倡导将真正(proper)过程变换成续体传递风格,并附带消除标签goto语句,但是他没有使用“续体”这个名字。

Steve Russell为他给IBM 704LISP实现的一个用户,发明了续体来解决Template:Le问题[22],但是他没有为其命名。

指称语义领域的研究中,学者采用“续体”来规定一个控制构造对整个程序执行最终结果的贡献[23][24]Christopher Strachey和Christopher P. Wadsworth在1974年技术专著《续体:处理完全跳转的数学语义》中给出了一个“小型续体语言”示例[25]。续体的概念在指称语义中定型于Template:Le在1977年出版的著作《指称语义Scott-Strachey编程语言理论途径》[26]

Scheme语言的1975年最初版本提供了续体算子,即源自Maclisp的用于例外处理的命名catch/throw[27][28]Template:Le和Uwe F. Pleban在1980年论文《LISP与SCHEME的语义比较》中为Scheme语言介入了基于“续体语义”的指称语义规定[29][30]

Daniel P. FriedmanTemplate:LeTemplate:Le基于按照Scheme语言的“续体语义”而编写的编译器[31][32],在1984年为Scheme介入了头等续体算子Template:Le(简写为call/cc[33][34],随后还介入了其约束形式[35]。当前Scheme语言报告中的形式语义描述,沿袭了指称语义一系著作中所采用的概念表示法[36][37][38]

Bruce Duba等人在1991年将头等续体模块介入到了SML/NJ[39]CONT签名具有接口val callcc : ('a cont -> 'a) -> 'aval throw : 'a cont -> 'a -> 'b[40]。这里的'a cont是接受类型参数'a的续体类型,callcc f应用f到当前续体,如果f以实际参数x调用这个续体,则如同(callcc f)返回x作为结果。throw k a以实际参数a调用续体k;它在本质上将续体强制转变成为了函数,故而在每次续体调用之时重新介入了一个独立的类型参数'b

续体传递风格[编辑]

脚本错误:没有“main”这个模块。 续体的概念主要起源于计算模型研究,比如λ演算[41]指称语义[25]演员模型[42]。这些模型仰仗于编程者或语义工程师书写数学函数时采用“续体传递风格”(continuation-passing style,简写为CPS)[43]。续体传递风格(CPS)意味着编程中用到的每个函数,都接纳并于结束时应用一个表示有关于这个函数调用的余下计算的函数。Template:Le的最内部分必须首先求值,将表达式转换为续体传递风格等价者,比如Template:Le在1972年论文《Lambda演算基模》中所举的例子[41]:将<math display="inline">\;(f\ (g\ (h\ a)))\;</math>变换成<math display="inline">\;(\hat{h}\ (\lambda x\ .\ (\hat{g}\ (\lambda y\ .\ (f\ y))\ x))\ a)\;</math>,具有将表达式“从内至外”翻转的效果,从而使求值次序以及控制流程变得更加明确。

下面是Standard ML中的高阶函数foldrmap的续体传递风格实现,和达成一个整数列表的合计函数的简单用例[44]

fun foldr' f b l = let
    fun cf ([], k) = k b
      | cf (a :: r, k) = cf (r, fn x => k (f (a, x)))
    in
        cf (l, fn x => x)
    end

fun map' f l = foldr' (fn (x, y) => (f x) :: y) [] l 

fun sum l = foldr' (fn (x, y) => (x + y)) 0 l

所有CPS函数比如这里的cf,都接受一个额外的实际参数比如这里的k,它被称为续体。在CPS函数调用中的所有实际参数,必须要么是一个λ表达式比如这里fn x => k (f (a, x)),它将续体k应用于函数f部分应用,要么是一个变量比如这里的lr,而不能是更复杂的表达式。当CPS函数已经计算出来其结果值的时候,它通过以结果值作为实际参数调用续体函数来返回它,比如这里的cf ([], k) = k b,在计算的任何步骤中只要直接返回结果值就会终止整个计算。一个函数比如这里的foldr',要调用CPS函数比如这里的cf,就必须提供一个函数比如这里的fn x => x,用来接受它所调用的CPS函数的结果值。

对于输入[e1, e2, …, en],这里的sum函数等价于将函数复合(fn x => x)∘(fn x => e1 + x)∘(fn x => e2 + x)∘…∘(fn x => en + x),应用于0之上,它得到(e1 + (e2 + (… + (en + 0)…)))。此外,建立在惰性求值柯里化之上的Haskell提供了函数复合算子

下面是在Scheme语言中仅使用其基本形式的几个例子,对比了直接风格代码和对应的CPS代码,这里约定了将续体函数表示为名为k的形式参数:

直接风格
续体传递风格
(define (pythag x y)
  (sqrt (+ (* x x) (* y y))))
(define (pythag& x y k)
  (*& x x (lambda (a)
    (*& y y (lambda (b)
      (+& a b (lambda (c)
        (sqrt& c k))))))))
(define (factorial n)
  (if (= n 0)
    1
    (* n (factorial (- n 1)))))
(define (factorial& n k)
  (=& n 0 (lambda (t)
    (if t
      (k 1)
      (-& n 1 (lambda (n-1)
        (factorial& n-1 (lambda (acc)
          (*& n acc k)))))))))
(define (factorial n)
  (define (loop n acc)
    (if (= n 0)
      acc
      (loop (- n 1) (* n acc))))
  (loop n 1))
(define (factorial& n k)
  (define (loop& n acc k)
    (=& n 0 (lambda (t)
      (if t
        (k acc)
        (-& n 1 (lambda (n-1) 
          (*& n acc (lambda (n*acc)
            (loop& n-1 n*acc k)))))))))
  (loop& n 1 k))

对于函数式编程者而言,以续体传递风格表达代码使得在直接风格中隐含的一些事物显露出来:中介值或临时值都被指定了名字[45],实际参数求值的次序变为显见;随之而来,过程返回变成了对续体的调用,而尾调用变成将传递给这个调用者的续体不加修改的传递给被调用过程。

这里翻转条件表达式的方式类似于Smalltalk中遵循邱奇布尔值编码的条件执行控制结构

result := a > b
  ifTrue: [ 'greater' ]
  ifFalse: [ 'less or equal' ]

注意在CPS版本的代码中,使用的函数原语(functional primitive)[46],比如这里的*&+&-&=&sqrt&,自身也是CPS而非直接风格,所以要使得上述例子在Scheme系统中运行,就必须写出这些函数原语的CPS版本,例如=&定义为:

(define (=& x y k)
  (k (= x y)))

一般编程在写CPS函数之时,经常不采用CPS函数原语,比如将前面的阶乘函数写为:

(define (factorial& n k)
  (if (= n 0)
    (k 1)
    (factorial& 
      (- n 1) 
      (lambda (acc) (k (* n acc))))))

要在REPL或直接风格函数中调用CPS函数,必须提供接受CPS函数计算结果的一个续体,比如恒等函数

(pythag& 3 4 (lambda (x) x))
(factorial& 4 (lambda (x) x))

采用续体传递风格使函数式编程者能获得以任意方式操纵控制流程的表达能力,代价是手工维护控制续体通常是高度复杂的任务。终极解决方案是用Scheme写一组转换例程,将Scheme的直接风格Template:Le转换成CPS表达式[47],下面的例子仅将直接风格函数原语转换成CPS函数原语:

(define (cps-prim f)
  (lambda args
    (let ((r (reverse args)))
      ((car r) (apply f (reverse (cdr r)))))))

(define =& (cps-prim =))
(define *& (cps-prim *))
(define +& (cps-prim +))
(define -& (cps-prim -))
(define sqrt& (cps-prim sqrt))

这里的reverse函数反转LISP列表所用的单向链表,其首次应用将原来尾部元素反转到了首部,接着取出这个元素应用并再次应用reverse函数将余下诸元素反转成原来次序。Template:Le函数的应用(apply f args),以args列表的诸元素作为实际参数调用f函数。

函数evalTemplate:Leapply[48],是传统元循环求值器的两大中心构件[49]Daniel P. FriedmanTemplate:Le,借鉴了Template:Le的扩展了续体的解释器[50],在1984年论文《续体与协程》中,介入了基于Template:Le(控制、环境和续体)的元循环解释器[51],并在其著作《Template:Le》中发展出了“续体传递解释器”。

以当前续体调用[编辑]

脚本错误:没有“main”这个模块。

非形式化描述[编辑]

Scheme语言中,每当一个Scheme表达式被求值的时候,就会有一个续体想要这个表达式的结果。续体表示了这个计算的全部抑或缺省的未来。例如,如果这个表达式被求值于顶层,那么续体将接受这个结果,将它打印到屏幕上,提示下一次输入,进行求值,周而复始。多数时候续体包括用户代码所指定的动作,比如在接受这个结果的续体中,向它乘以存储在一个局部变量中的值再加上7,并将答案给予顶层续体来打印它。通常情况下编程者不用过多考虑那些隐藏在幕后的无处不在的续体。

Scheme语言为需要显式的处理续体的场合,提供了实化控制流程算子Template:Le(简写为call/cc),call/cc将当前续体包装(package up)成为一个“逃脱过程”(escape procedure),即具有Template:Le作为其唯一的运算,并把它作为实际参数传递给Scheme编程者所定义的函数。call/cc所包装的续体是头等对象,逃脱过程有着无限制的Template:Le(extent)[52],可以被存储在变量或数据结构之中,它可以按需要多次调用。

Scheme语言中,在call/cc的调用(call/cc f)中的函数f,接受包装了当前续体的逃脱过程作为其唯一实际参数。在后续执行中将逃脱过程应用于一个实际参数之时,此刻生效的续体被舍弃,转而使用创将这个逃脱过程之时生效的那个续体,控制流程将在这个续体被捕获的那一点上继续,而传递给这个逃脱过程的实际参数则变成这个call/cc调用的返回值。本章节的Scheme代码解说中出现的词语“当前续体”或“续体”,一般就是指在Scheme语言报告中描述的这个可操纵的包装它的“逃脱过程”。

归约语义[编辑]

callcc形式语义,除了采用指称语义中进行CPS变换的方式来给出[53][54],也可以采用操作语义中小步语义的方式来定义,即使用在上下文<math>\,C\,</math>之下的Template:Le规则[55][56]

<math>

\begin{align} C[\operatorname{callcc} (\lambda k.\, e)] &\;\to\; C[(\lambda k.\, e)(\lambda x.\, C[x])] \\ C[\operatorname{throw} (\lambda x.\, k)\, v] &\;\to\; (\lambda x.\, k)\, v \end{align} </math> 这里的callcc捕获的当前续体是<math>\,\lambda x.\, C[x]\,</math>,其中<math>\,x\,</math>是不被<math>\,C\,</math>所绑定的变量。下面是这种callcc在类似OCaml的假设语言中的用例[57]

2 * callcc (fun k -> 1 + (throw k 0));;

使用具有上下文C = 2 * [ ]callcc规则,对整个程序也就是这个表达式进行归约:

 2 * (fun k -> 1 + (throw k 0)) (fun x -> 2 * x) 2 * (1 + (throw (fun x -> 2 * x) 0))

接着使用具有上下文C = 2 * (1 + [ ])throw规则,继续进行归约:

 (fun x -> 2 * x) 0  2 * 0  0

Scheme编程语言示例[编辑]

立即返回[编辑]

下面的例子中,使用call/cc来模拟C风格语言中的return语句

(define (f return)                                                                                                                               
  (return 2)                                                                                                                                     
  3)

(f (lambda (x) x))
===> 3

(call/cc f)
===> 2

第一个演示,以恒等函数(lambda (x) x)作为实际参数调用函数f,函数f将绑定到形式参数return上的恒等函数应用于2,接着执行最后一个表达式3,从而函数f返回3。第二个演示,将call/cc应用于函数f,函数f将绑定到形式参数return上的续体应用于2,这在控制流程上等价于非局部跳转回到调用(call/cc f)的那一点上,将其Template:Le为返回值2

生成器[编辑]

下面是生成器的实现代码:

(define (generator lst)
  (define iterator (lambda (yield)
    (for-each (lambda (item)
      (set! yield (call/cc (lambda (resume)
        (set! iterator resume)
        (yield item)))))
      lst)
    (yield 'stop-iteration)))
  (lambda () (call/cc iterator)))

在这里于生成器之中定义它所生成的迭代器函数iterator,和随后定义函数generate-digit的时候,采用了函数定义的不加语法糖原始形式call/cc在这里被用到了两处:一处是(call/cc iterator),它以当前取用遍历返回值续体调用迭代器;另一处是(call/cc (lambda (resume) ……)),它在迭代器遍历目标列表之时捕获当前位置,用于下次重入迭代器之时于此处恢复执行。下面是上述代码的简单用例:

(define generate-digit
  (generator '(0 1 2)))

(define (display-two-digits)
  (display (generate-digit)) (newline)
  (display (generate-digit)) (newline))

(display-two-digits) ;; 分两行打印 0 和 1
(display-two-digits) ;; 分两行打印 2 和 stop-iteration

这里定义的函数generate-digit,是将函数generator应用于聚集(aggregate)即这里的列表'(0 1 2)之上,而得到的闭包lambda () (call/cc iterator))

在第一次执行(generate-digit)之时,执行(call/cc iterator),进而执行(iterator yield),它将取用遍历返回值续体绑定到形式参数yield之上;然后开始遍历列表的元素进行迭代的(for-each (lambda (item) ……) lst),在求值(set! yield (……))的第二个实际参数之时,进行每一次迭代步骤(call/cc (lambda (resume) ……)),其中的(set! iterator resume),将绑定到变量resume上的当前续体,重新绑定到函数名字iterator之上用于以后恢复执行,最后执行暂停表达式(yield item)返回当前列表项目。

在下一次执行(generate-digit)之时,(call/cc iterator)iterator所绑定的续体应用于当前取用遍历返回值续体之上,从而在上次迭代的(set! yield (call/cc ……))之处恢复执行,这个函数的实际参数求值完毕,它的执行将新传入的取用遍历返回值续体绑定到变量yield上,此后继续这一次的迭代步骤。在遍历了列表的元素之后迭代结束,最终执行(yield 'stop-iteration),返回一个约定的Template:Le常量。

协程[编辑]

call/cc还可以表达其他复杂的原始运算比如协程[58]。下面的代码使用续体达成协程协作式多任务用户级线程

(define *ready-list* '())

(define (yield)
  (call/cc (lambda (reenter)
    (let ((cont (car *ready-list*)))
      (set! *ready-list* (append (cdr *ready-list*) (list reenter)))
      (cont #f)))))

(define (fork fn . args)
  (call/cc (lambda (abort)
    (set! *ready-list* (cons abort *ready-list*))
    (yield)
    (apply fn args)
    (let ((cont (car *ready-list*)))
      (set! *ready-list* (cdr *ready-list*)) 
      (cont #f)))))

(define (schedule)
  (let loop ()
    (if (not (null? *ready-list*)) (begin
      (yield)
      (loop)))))

这里的全局变量*ready-list*是就绪线程列表,它存储处于脱离(detached)状态的就绪线程的重入续体;在选择重入线程的时候,将就绪线程列表头部的续体从列表中取出,并应用它以重入对应线程。退让过程yield,捕获对应于调用者的当前续体,将其追加到就绪线程列表的尾部,取出并应用就绪线程列表头部的续体而使本线程脱离运行(operating)。

分叉过程fork,接受一个函数fn和相应的参数列表args,它首先捕获自身的当前续体并将其添加到就绪线程列表的头部;然后调用退让过程yield,从而创建了一个用来创建工人(worker)线程的线程,接着取出并应用就绪线程列表头部的续体而使本线程脱离运行;由于分叉过程自身的续体已经添加到了就绪线程列表头部,重入它而随即结束分叉过程的这次调用。当重入这个创建工人线程的线程之时,调用函数fn,预期这个函数在自身之中调用退让过程yield,从而创建重入函数fn内部的工人线程。当工人线程最终并未调用退让过程而从函数fn直接退出回到分叉过程之时,必须接着以取出并应用就绪线程列表头部的续体的方式来结束这个工人线程,因为对分叉过程的调用早已结束。

调度过程schedule,持续检测就绪线程列表,只要有任何其他线程等待就调用退让过程yield,取出并应用就绪线程列表头部的续体而使本线程脱离运行,最终在无就绪者等待之时结束。调度过程在每一轮并发运行循环中只运行一次。调度过程在所有分叉过程之后调用,它起到的根本作用是充当会合点,即并发运行的多个线程中的最终剩下的唯一线程。下面是上述代码的简单用例:

(import (srfi 28))

(define (echo-string-n-times str n)
  (let loop ((i 0))
    (if (< i n) (begin
      (display (format "~a ~a~%" str i))
      (yield)
      (loop (+ i 1))))))

(define (main)
  (fork echo-string-n-times "This is AAA" 3)
  (fork echo-string-n-times "Hello from BBB" 2)
  (schedule))

(main)

其输出为:

This is AAA 0
Hello from BBB 0
This is AAA 1
Hello from BBB 1
This is AAA 2

在这个用例的两个分叉过程中,前面的分叉过程先执行并且后结束,如果不介入调度过程转而完全由工人线程自行调度,会合于此处将导致非预期的情况出现,即除非就此退出这个进程,在其后的分叉过程会再次执行。

最后演示对上述协程代码的一种变化:将退让过程yield改成具有返回值,调用了退让过程的分叉过程fork也相应更改;将调度过程schedule改为采用Template:Le策略,它与前面代码的区别在于将调度过程自身的续体添加到就绪线程列表的头部;并为调度过程增加了一个函数参数fn,用这个函数来处理工人线程的返回值。这种调度过程也可以改名为分派过程dispatch

(define *ready-list* '())

(define (yield x)
  (call/cc (lambda (reenter)
    (let ((cont (car *ready-list*)))
      (set! *ready-list* (append (cdr *ready-list*) (list reenter)))
      (cont x)))))

(define (fork fn . args)
  (call/cc (lambda (abort)
    (set! *ready-list* (cons abort *ready-list*))
    (yield #f)
    (apply fn args)
    (let ((cont (car *ready-list*)))
      (set! *ready-list* (cdr *ready-list*)) 
      (cont #f)))))

(define (schedule fn)
  (let loop ()
    (if (not (null? *ready-list*)) (begin
      (fn (call/cc (lambda (return)
        (let ((cont (car *ready-list*)))
          (set! *ready-list* (cons return (cdr *ready-list*)))
          (cont #f)))))
      (loop)))))

这种协程也称为“半协程”或“生成器”,它与前面例子中同名的生成器机制各有用途。下面是其用例:

(import (srfi 28))

(define (echo-string-n-times str n)
  (let loop ((i 0))
    (if (< i n) (begin
      (yield (format "~a ~a~%" str i))
      (loop (+ i 1))))))

(define (main)
  (fork echo-string-n-times "This is AAA" 3)
  (fork echo-string-n-times "Hello from BBB" 2)
  (schedule display))

(main)

编程语言的直接支持[编辑]

除了SchemeRacket新泽西Standard ML之外,一些编程语言直接支持头等续体比如:

页面Template:Div col/styles.css没有内容。

参见[编辑]

引用与注释[编辑]

  1. https://link.springer.com/chapter/10.1007/978-3-642-03034-5_17
  2. https://dl.acm.org/doi/10.5555/3327546.3327682
  3. 脚本错误:没有“citation/CS1”这个模块。
  4. 脚本错误:没有“citation/CS1”这个模块。
  5. 脚本错误:没有“citation/CS1”这个模块。
  6. 脚本错误:没有“citation/CS1”这个模块。
  7. 脚本错误:没有“citation/CS1”这个模块。
  8. 脚本错误:没有“citation/CS1”这个模块。
  9. 脚本错误:没有“citation/CS1”这个模块。
  10. 脚本错误:没有“citation/CS1”这个模块。
  11. 脚本错误:没有“Citation/CS1”这个模块。
  12. 脚本错误:没有“citation/CS1”这个模块。
  13. 脚本错误:没有“Citation/CS1”这个模块。
  14. 脚本错误:没有“citation/CS1”这个模块。
  15. 脚本错误:没有“Citation/CS1”这个模块。
  16. 脚本错误:没有“citation/CS1”这个模块。
  17. 脚本错误:没有“citation/CS1”这个模块。
  18. 脚本错误:没有“citation/CS1”这个模块。
  19. 脚本错误:没有“citation/CS1”这个模块。 “ Reynolds uses the term "continuation" in [Reynolds 72]. Church clearly understood the use of continuations; it is the only way to get anything accomplished at all in pure lambda calculus! For example, examine his definition of ordered pairs and triads on page 30 of [Church 41]. In SCHEME notation. this is:
      [M, N] means (LAMBDA (A) (A M N))
      21 means (LAMBDA (A) (A (LAMBDA (B C) B)))
      22 means (LAMBDA (A) (A (LAMBDA (B C) C)))
    where 21 e.g. selects the first element of a pair. (Note that these functions are isomorphic to CONS, CAR, and CDR!) ”
  20. 脚本错误:没有“Footnotes”这个模块。
  21. 脚本错误:没有“citation/CS1”这个模块。
  22. 脚本错误:没有“citation/CS1”这个模块。
  23. 脚本错误:没有“citation/CS1”这个模块。
  24. 脚本错误:没有“citation/CS1”这个模块。
  25. 25.0 25.1 脚本错误:没有“citation/CS1”这个模块。
  26. 脚本错误:没有“citation/CS1”这个模块。
  27. Gerald J. Sussman, Guy L. Steele Jr.. 链接至维基文库 Scheme: An Interpreter for Extended Lambda Calculus. 维基文库. 1975 (英文). CATCH - This is the "escape operator" which gives the user a handle on the control structure of the interpreter. The expression:
      (CATCH <identifier> <expression>)
    evaluates <expression> in an environment where <identifier> is bound to a continuation which is "just about to return from the CATCH"; that is, if the continuation is called as a function of one argument, then control proceeds as if the CATCH expression had returned with the supplied (evaluated) argument as its value. ……
    As another example, we can define a THROW function, which may then be used with CATCH much as they are in LISP:
      (DEFINE THROW (LAMBDA (TAG RESULT) (TAG RESULT)))
     
  28. 脚本错误:没有“citation/CS1”这个模块。
  29. 脚本错误:没有“citation/CS1”这个模块。
  30. 脚本错误:没有“citation/CS1”这个模块。
  31. 脚本错误:没有“citation/CS1”这个模块。
  32. 脚本错误:没有“citation/CS1”这个模块。
  33. 脚本错误:没有“citation/CS1”这个模块。
  34. 脚本错误:没有“citation/CS1”这个模块。
  35. 脚本错误:没有“citation/CS1”这个模块。
  36. 脚本错误:没有“citation/CS1”这个模块。
  37. 脚本错误:没有“citation/CS1”这个模块。
  38. 脚本错误:没有“citation/CS1”这个模块。
  39. 脚本错误:没有“citation/CS1”这个模块。
  40. 脚本错误:没有“citation/CS1”这个模块。
  41. 41.0 41.1 脚本错误:没有“citation/CS1”这个模块。
  42. 脚本错误:没有“citation/CS1”这个模块。
  43. Gerald J. Sussman, Guy L. Steele Jr. 链接至维基文库 Scheme: An Interpreter for Extended Lambda Calculus. 维基文库. 1975 (英文). It is always possible, ……to perform any calculation in this way: rather than reducing to its value, it reduces to an application of a continuation to its value (cf. [[[:Template:Le]]]). That is, in this continuation-passing programming style, a function always "returns" its result by "sending" it to another function. 
  44. 脚本错误:没有“citation/CS1”这个模块。
    脚本错误:没有“citation/CS1”这个模块。
    脚本错误:没有“citation/CS1”这个模块。
  45. 脚本错误:没有“citation/CS1”这个模块。
  46. 脚本错误:没有“citation/CS1”这个模块。
    脚本错误:没有“citation/CS1”这个模块。
  47. 脚本错误:没有“citation/CS1”这个模块。
  48. 脚本错误:没有“Citation/CS1”这个模块。
  49. 脚本错误:没有“citation/CS1”这个模块。
  50. 脚本错误:没有“citation/CS1”这个模块。
  51. 脚本错误:没有“citation/CS1”这个模块。“In a continuation semantics, every recursive procedure receives a continuation parameter. Rather than simply return, the procedure either passes its result directly to this continuation, or passes the continuation (possibly embellished) to some other procedure. ……
    (define meaning 
      (lambda (e r k) ; e = expression, r = environment, k = continuation
        (case (type-of-expression e)
          [constant (k e)]
          [identifier (k (R-lookup e r))]
          [lambda (k (lambda (actuals k)
            (evaluate-all (body-pts e)
              (extend-env r (formals-pt e) actuals)
              k)))]
          [if (meaning (test-pt e) r
            (lambda (v) (if v
              (meaning (then-pt e) r k)
              (meaning (else-pt e) r k))))]
          [set! (meaning (val-pt e) r
            (lambda (v) (k (store! (L-lookup (id-pt e) r) v))))]
          [call/cc (meaning (fn-pt e) r
            (lambda (f)
              (f
                (list (lambda (actuals dummy) (k (car actuals)))) 
                k)))]
          [application (meaning-of-all (comb-parts e) r
            (lambda (vals) ((car vals) (cdr vals) k)))])))
    
    (define evaluate-all
      (lambda (exp-list r k)
        (meaning (car exp-list) r
          (if (null? (cdr exp-list))
            k
            (lambda (v) (evaluate-all (cdr exp-list) r k))))))
    
    (define meaning-of-all
      (lambda (exp-list r k)
        (meaning (car exp-list) r
          (lambda (v) (if (null? (cdr exp-list))
            (k (cons v nil))
            (meaning-of-all (cdr exp-list) r
              (lambda (vr) (k (cons v vr)))))))))
    

    Figure 1: Meta-circular interpreter for a subset of Scheme 84”

  52. 脚本错误:没有“citation/CS1”这个模块。
  53. 脚本错误:没有“citation/CS1”这个模块。
  54. 脚本错误:没有“citation/CS1”这个模块。
  55. 脚本错误:没有“citation/CS1”这个模块。
  56. 脚本错误:没有“citation/CS1”这个模块。
  57. 脚本错误:没有“citation/CS1”这个模块。
  58. 脚本错误:没有“citation/CS1”这个模块。
  59. 脚本错误:没有“citation/CS1”这个模块。
  60. 脚本错误:没有“citation/CS1”这个模块。
  61. 脚本错误:没有“citation/CS1”这个模块。
  62. 脚本错误:没有“citation/CS1”这个模块。
  63. 脚本错误:没有“citation/CS1”这个模块。
  64. 脚本错误:没有“citation/CS1”这个模块。
  65. 脚本错误:没有“citation/CS1”这个模块。
  66. 脚本错误:没有“citation/CS1”这个模块。
  67. 脚本错误:没有“citation/CS1”这个模块。

延伸阅读[编辑]

页面Template:ReflistH/styles.css没有内容。

  • 脚本错误:没有“citation/CS1”这个模块。
  • 脚本错误:没有“citation/CS1”这个模块。
  • 脚本错误:没有“citation/CS1”这个模块。 脚本错误:没有“citation/CS1”这个模块。
  • 脚本错误:没有“citation/CS1”这个模块。
  • 脚本错误:没有“citation/CS1”这个模块。
  • 脚本错误:没有“citation/CS1”这个模块。
  • 脚本错误:没有“citation/CS1”这个模块。
  • 脚本错误:没有“citation/CS1”这个模块。
  • 脚本错误:没有“Citation/CS1”这个模块。
  • 脚本错误:没有“citation/CS1”这个模块。
  • 脚本错误:没有“citation/CS1”这个模块。
  • 脚本错误:没有“citation/CS1”这个模块。

外部链接[编辑]


package.lua第80行Lua错误:module 'Module:Arguments' not found