call/cc 总结 | Scheme

call/cc 总结 | Scheme

来源 https://www.sczyh30.com/posts/Functional-Programming/call-with-current-continuation/

Continuation

Continuation 也是一个老生常谈的东西了,我们来回顾一下。首先我们看一下 TSPL4 中定义的表达式求值需要做的事:

During the evaluation of a Scheme expression, the implementation must keep track of two things: (1) what to evaluate and (2) what to do with the value.

Continuation 即为其中的(2),即表达式被求值以后,接下来要对表达式做的计算R5RS 中 continuation 的定义为:

The continuation represents an entire (default) future for the computation.

比如 (+ (* 2 3) (+ 1 7)) 表达式中,(* 2 3)的 continuation 为:保存 (* 2 3) 计算出的值 6,然后计算 (+ 1 7) 的值,最后将两表达式的值相加,结束;(+ 1 7) 的 continuation 为:保存 (+ 1 7) 的值 8,将其与前面计算出的 6 相加,结束。

Scheme 中的 continuation 是 first-class 的,也就是说它可以被当做参数进行传递和返回;并且 Scheme 中可以将 continuation 视为一个 procedure,也就是说可以调用 continuation 执行后续的运算。

call/cc

每个表达式在求值的时候,都会有一个对应的 current continuation,它在等着当前表达式求值完毕然后把值传递给它。那么如何捕捉 current continuation 呢?这就要用到 Scheme 中强大的 call/cc了。call/cc 的全名是 call-with-current-continuation ,它可以捕捉当前环境下的 current continuation 并利用它做各种各样的事情,如改变控制流,实现非本地退出(non-local exit)、协程(coroutine)、多任务(multi-tasking)等,非常方便。注意这里的 continuation 将当前 context 一并打包保存起来了,而不只是保存程序运行的位置。下面我们来举几个例子说明一下 call/cc 的用法。

current continuation

我们先来看个最简单的例子 —— 用它来捕捉 current continuation 并作为 procedure 调用。call/cc 接受一个函数,该函数接受一个参数,此参数即为 current continuation。以之前 (+ (* 2 3) (+ 1 7)) 表达式中 (* 2 3) 的 continuation 为例:


1

2

3

4

5


(define cc #f)

(+ (call/cc (lambda (return)

(set! cc return)

(* 2 3)))

(+ 1 7))

我们将 (* 2 3) 的 current continuation (用(+ ? (+ 1 7))表示) 绑定给 cc 变量。现在 cc 就对应了一个 continuation ,它相当于过程 (define (cc x) (+ (x) (+ 1 7))),等待一个值然后进行后续的运算:


1

2

3

4

5

6


> cc

#<continuation>

> (cc 10)

18

> (cc (* 2 3))

14

这个例子很好理解,我们下面引入 call/cc 的本质 —— 控制流变换。在 Scheme 中,假设 call/cc 捕捉到的 current continuation 为 cc(位于 lambda 中),如果 cc 作为过程 直接或间接地被调用(即给它传值),call/cc 会立即返回,返回值即为传入 cc 的值。即一旦 current continuation 被调用,控制流会跳到 call/cc 处。因此,利用 call/cc,我们可以摆脱顺序执行的限制,在程序中跳来跳去,非常灵活。下面我们举几个 non-local exit 的例子来说明。

Non-local exit

Scheme 中没有 break 和 return 关键字,因此在循环中如果想 break 并提前返回的话就得借助 call/cc。比如下面的例子寻找传入的 list 中是否包含 5


1

2

3

4

5

6

7

8

9

10

11

12


(define (do-with element return)

(if (= element 5)

(return ‘find-five)

(void)))

(define (check-lst lst)

(call/cc (lambda (return)

(for-each (lambda (element)

(do-with element return)

(printf "~a~%" element))

lst)

‘not-found)))

测试:


1

2

3

4

5

6

7

8

9


> (check-lst ‘(0 2 4))

0

2

4

‘not-found

> (check-lst ‘(0 3 5 1))

0

3

‘find-five

check-lst 过程会遍历列表中的元素,每次都会将 current continuation 传给 do-with 过程并进行调用,一旦do-with 遇到 5,我们就将结果传给 current continuation (即 return),此时控制流会马上跳回 check-lst 过程中的 call/cc 处,这时候就已经终止遍历了(跳出了循环)。call/cc 的返回值为 ‘find-five,所以最后会在控制台上打印出 ‘find-five

我们再来看一个经典的 generator 的例子,它非常像 Python 和 ES 6 中的 yield,每次调用的时候都会返回 list 中的一个元素:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23


(define (generate-one-element-at-a-time lst)

;; Hand the next item from a-list to "return" or an end-of-list marker

(define (control-state return)

(for-each

(lambda (element)

(set! return (call/cc

(lambda (resume-here)

;; Grab the current continuation

(set! control-state resume-here)

(return element)))))

lst)

(return ‘you-fell-off-the-end))

;; This is the actual generator, producing one item from a-list at a time

(define (generator)

(call/cc control-state))

;; Return the generator

generator)

(define generate-digit

(generate-one-element-at-a-time ‘(0 1 2)))

调用:


1

2

3

4

5

6

7

8


> (generate-digit)

0

> (generate-digit)

1

> (generate-digit)

2

> (generate-digit)

‘you-fell-off-the-end

注意到这个例子里有两个 call/cc,大家刚看到的时候可能会有点晕,其实这两个 call/cc 各司其职,互不干扰。第一个 call/cc 负责保存遍历的状态(从此处恢复),而 generator 中的 call/cc 才是真正生成值的地方(非本地退出)。其中一个需要注意的地方就是 control-state,它在第一次调用的时候还是个 procedure,在第一次调用的过程中它就被重新绑定成一个 continuation,之后再调用 generator 生成器的时候,控制流就可以跳到之前遍历的位置继续执行下面的过程,从而达到生成器的效果。

阴阳谜题

continuation 环境嵌套。后面有时间专开一篇分析~


1

2

3

4

5


(let* ((yin

((lambda (cc) (display #\@) cc) (call-with-current-continuation (lambda (c) c))))

(yang

((lambda (cc) (display #\*) cc) (call-with-current-continuation (lambda (c) c)))))

(yin yang))

call/cc与数理逻辑

这里简单提一下 call/cc 与类型系统和数理逻辑的联系。call/cc 的类型是 ((P → Q) → P) → P,通过 Curry-Howard 同构,它可以对应到经典逻辑中的 Peirce’s law

((P→Q)→P)→P((P→Q)→P)→P

Peirce’s law 代表排中律 P∧?PP∧?P,这条逻辑无法在 λ演算所对应的直觉逻辑中表示(直觉逻辑中双重否定不成立),因此 call/cc 无法用 λ表达式定义。通常我们用扩展后的 λμ calculusλμ calculus 来定义 call/cc,λμ calculusλμ calculus 经 Curry-Howard 同构可以得到经典逻辑。


References

call/cc的类型是什么

原文:https://blog.csdn.net/nklofy/article/details/48999417

这篇来自我在某问答网站上的一个回答。但我觉得这个问题很有价值,我思考和写作的时间花费了很久。所以我觉得应该保存起来。

问题是,call/cc的类型是什么。我们知道,(call/cc (lambda (k) p))有两种用法,一种是(call/cc (lambda (k) (k a))),例如(+ 1(call/cc (lambda (k) (k 2))));一种是(call/cc (lambda (k) k)),例如(let a (call/cc (lambda (k) k)))。第一种返回a,并送入continuation计算;第二种直接返回一个continuation。

所以很明显,call/cc不是只有一种返回类型。两种用法对应两种不同类型。第一个类型是((T1 -> T2) -> T1) -> (T1 -> T2) -> T1,第二种是( (T1 -> T2) -> (T1 -> T2) ) -> (T1 -> T2) -> (T1 -> T2)。

一个简单的联想是,假设x类型是T1->T2, a类型是T1,那么 ((λ(k) (k a) ) x)和 (λ(k) (k) ) x)分别返回什么类型?前者是T2,后者是T1->T2。这就是所谓函数一等公民。

回到call/cc。首先要分析整个程序到call/cc之前的位置,按照TAPL的表达方式,此时的continuation类型是:
λ k: T1 . T1 -> T2

从这个位置开始,call/cc被调用,evaluation rules可表示为:
call/cc [k |-> continuation ] λ k. k a --> a。                    E-APP1 (第一种情况) 
call/cc [k |-> continuation ] λ k. k --> continuation。       E-APP2 (第二种情况)

第一种情况下,type rule为:

Γ├ continuation: T1->T2 Γ├ a: T1 Γ├ λ k: T1 -> T2 . k a
-------------------------------------------------------------------------------------- T-APP1
Γ├ (call/cc ( λ k . k a )) continuation : T1

call/cc过程返回类型T1。原因是evaluation rule对应的是E-APP1。

第二种情况下type rule:

Γ├ continuation: T1->T2 Γ├ λ k: T1 -> T2 . k
-------------------------------------------------------------------------------------- T-APP2 
Γ├ (call/cc ( λ k . k)) continuation : T1->T2

即该情况下应该返回T1->T2。

所以结论就是,两种情况下,返回的类型是不一样的。call/cc有两种可能的返回类型,返回哪一种根据不同的(λ (k) process)匹配。一种匹配 k a,另一种匹配 k。是的,这就类似于(λ x . x a ) 与(λ x . x ) 的区别,返回类型不一样很正常。

在第二种情况下process 直接返回k,但其实程序中call/cc 前后通常会有个let、set!或者其他赋值函数,将这个continuation保存到某个变量x,程序后面会调用(x a)的形式。对于(x a)或者之前的(continuation a),都回到了(T1 -> T2) -> T1 -> T2的套路上,程序最终运行完时两种情况殊途同归。

PS,在我看来,call/cc更接近于宏而非函数,往往纯用于结构跳转而非求值,例如call/cc (lambda (k) (if(something) (k #t)))...)这种用法。它的精华放在(k #t)以外的地方,控制运行还是跳转。还有,scheme本来就是动态类型系统,类型可以任意的变,分析起来非常痛苦。若当作宏来看就顺眼多了,(...call/cc ...)这个括号里的内容整体用k一个符号代替。然后无论哪种用法,遇到k a或k b时,从整个程序中挖掉call/cc括号内容后,a或b代入k所在位置就能得到结果。

参考文献:<types and programming languages>

================= End

原文地址:https://www.cnblogs.com/lsgxeva/p/10159181.html

时间: 2024-10-25 16:07:04

call/cc 总结 | Scheme的相关文章

邓紫棋开唱惨被轰:滚出娱乐圈

http://www.cnpoc.cn/HZcommonShowFirst.asp?CID=122&CNAME=%B3%BB%D6%DD%CB%D5%CF%C9%C7%F8%D5%D2%D1%A7%C9%FA%C3%C3%C9%CF%C3%C5%B0%B4%C4%A6%B7%FE%CE%F1%A8%801858885v7572%a1%f8k5u21z http://www.cnpoc.cn/HZcommonShowFirst.asp?CID=122&CNAME=%B3%BB%D6%DD%C

览唾寐了酒rkv2v8h37gg8p6qys1d0

该人士表示,整体思路"就是一个放开一个监管,围绕市场化进行改革,同时防止国有资产流失". "国有资产流失是一顶非常可怕的帽子,很多人怕犯错误,不敢推进员工激励.混合所有制等改革." 该人士表示,整体思路"就是一个放开一个监管,围绕市场化进行改革,同时防止国有资产流失". 两年前,财政部为"什么人能够持股"一事,进行了多次讨论.财政部相关政策制定部门人士认为,目前最纠结的问题是:"哪些人可以持股?管理层.技术骨干还是全

《大唐传奇》今日“天镜”内测引爆激战狂潮

%C4%C4%C0%EF%D3%D0%C8%FD%C5%E3%D0%A1%BD%E3%B0%B4%C4%A6%B7%FE%CE%F11550%D2%BB1111%D2%BB580%A3%D6%C9%CF%C3%C5%A3%AD%C8%AB%CC%D7%A3%D6%A8%EF%A8%F5%A8%F8%A8%EF2014-11-29http://tuan.baidu.com/hot/query/45/%B5%A4%B6%AB %C4%C4%C0%EF%D3%D0%C8%FD%C5%E3%D0%A1%

商敲惺型巫sv5wbi48csmext

"十八届三中全会提出,在混合所有制企业可以搞员工持股,但这些事情比较谨慎,既有经验又有教训."该人士表示.随着国企改革的深入,2016年将有更多的国企改革亮点值得期待. 不久前,国务院国有企业改革领导小组办公室针对社会关注的热点.难点问题提出了将在2016年推进"十项改革试点".具体包括:一是落实董事会职权的试点:二是市场化选聘经营管理者的试点:三是推行职业经理人制度的试点:四是企业薪酬分配差异化改革的试点:五是国有资本投资运营公司的试点:六是中央企业兼并重组的试点

热圩死滔难wq42ceruh63ghfkk9ou

员工持股启动随着国企改革的深入,2016年将有更多的国企改革亮点值得期待. 前述国资委人士认为:"一般的企业不适合员工持股,一个万人大厂,所有员工都持股,没有意义.容易造成福利性的持股.实际上,改革30年,已经证明,全员持股不是一个好办法,要分企业.分行业,根据它的特点进行持股." 在不久前落幕的2016年全国两会上,国务院国有企业改革领导小组组长马凯对外界表示:"去年是国企改革方案制定的一年,今年是扎扎实实的落实年." <国有科技型企业股权和分红激励暂行办法

颇畚粕岩霖a8nys66u6515ut2erj

"十八届三中全会提出,在混合所有制企业可以搞员工持股,但这些事情比较谨慎,既有经验又有教训."该人士表示. 据报道,国务院国资委副秘书长彭华岗日前在深入推进国企国资改革研讨会上表示,今年将围绕重点难点问题开展"十项改革试点",通过试点取得突破.多点开花.彭华岗透露,过去一年国企改革取得重大突破和进展,17家省级国资委和40户中央企业提出方案.公司制股份制改革成效显著,全国国有企业改制面已达80%:法人治理结构不断完善,2015年中央企业建设规范董事会的企业增加11户

Nginx配置抵御DDOS或CC攻击

防攻击的思路我们都明白,比如限制IP啊,过滤攻击字符串啊,识别攻击指纹啦.可是要如何去实现它呢?用守护脚本吗?用PHP在外面包一层过滤?还是直接加防火墙吗?这些都是防御手段.不过本文将要介绍的是直接通过nginx的普通模块和配置文件的组合来达到一定的防御效果. 验证浏览器行为 简易版 下面就是nginx的配置文件写法. if ($cookie_say != "hbnl"){ add_header Set-Cookie "say=hbnl"; rewrite .* &

OpenResty(nginx扩展)实现防cc攻击

OpenResty(nginx扩展)实现防cc攻击 导读 OpenResty 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台.这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统 流程图 本文介绍使用openresty来实现防cc攻击的功能.ope

如何配置Nginx防御CC攻击

前言这次我们来讲讲如何通过简单的配置文件来实现nginx防御攻击的效果.其实很多时候,各种防攻击的思路我们都明白,比如限制IP啊,过滤攻击字符串啊,识别攻击指纹啦.可是要如何去实现它呢?用守护脚本吗?用PHP在外面包一层过滤?还是直接加防火墙吗?这些都是防御手段.不过本文将要介绍的是直接通过nginx的普通模块和配置文件的组合来达到一定的防御效果.验证浏览器行为简易版我们先来做个比喻.社区在搞福利,在广场上给大家派发红包.而坏人派了一批人形的机器人(没有语言模块)来冒领红包,聪明工作人员需要想出