Scheme 呼叫cc示例球拍
我正在分析有关Scheme 呼叫cc示例球拍,scheme,racket,continuations,callcc,Scheme,Racket,Continuations,Callcc,我正在分析有关call/cc使用的代码。这个函数有点神秘,要完全理解它相当复杂 我真的无法理解这个代码是如何工作的。下面是我的解释 (define (print+sub x y) (display x) (display " ") (display y) (display " -> ") (- x y)) (define (puzzle) (call/cc (lambda (exit) (defin
call/cc
使用的代码。这个函数有点神秘,要完全理解它相当复杂
我真的无法理解这个代码是如何工作的。下面是我的解释
(define (print+sub x y)
(display x)
(display " ")
(display y)
(display " -> ")
(- x y))
(define (puzzle)
(call/cc (lambda (exit)
(define (local e)
(call/cc
(lambda (local-exit)
(exit (print+sub e
(call/cc
(lambda (new-exit)
(set! exit new-exit)
(local-exit #f))))))))
(local 6)
(exit 2))))
(define x (puzzle))
call/cc
通过
call/cc (lambda(exit))
然后再次通过
(call/cc
(lambda (local-exit)
使用参数6
调用函数local
,该参数作为x
传递到print+sub
。但是值2
如何到达print+sub
作为y
最重要的是,所有这些指令的执行顺序是什么?调用(拼图)
设置一个continuationexit
,这样调用(exit val)
就好像调用(拼图)
刚刚返回了val
值一样
然后调用(本地6)
。它设置一个连续的本地出口
,这样调用(本地出口val2)
就如同调用(本地6)
刚刚返回了该val2
值一样。当然,返回值会被忽略,下一个调用,(退出2)
将在下一个调用中执行
现在,在设置了本地出口后
,调用(出口(打印+sub e…)
)。它需要首先找出(print+sub e…
的val3
值,以便将其传递给调用(exit val3)
print+sub
需要两个参数。调用中有两个表达式必须求值,因此找到的值(如果有)将作为x
和y
传递到print+sub
评估e
很简单。它是6
计算第二个表达式,(call/cc(lambda(new exit)…)
,设置另一个延续,new exit
,这样调用(new exit y)
相当于将y
返回到调用中等待它的插槽{y}
然后是
(lambda (new-exit)
(set! exit new-exit)
(local-exit #f))
输入(设置!退出新退出)
将任何调用(退出val)
的含义从现在起更改为与调用(新退出val)
的含义相同
现在,最后调用(本地出口#f)
。它跳出(本地6)
调用,立即返回#f
,然后被忽略。呼叫(退出2)
。这与呼叫(新出口2)
相同。这意味着将2
返回到{y}
插槽中,因此现在执行(打印+子E2)
内部(退出(打印+子E2))
调用
print+sub
打印它打印的内容并返回4
,因此现在调用(退出4)
现在关键的一点是,这里使用的exit
值是多少?是原来的退出
继续,还是更改后的新退出
假设方案标准规定,在任何函数应用程序中,首先计算(foo a1 a2…an)
foo
,然后以未指定的顺序计算ai
s,然后将函数值应用于刚刚找到的n
参数值。这意味着要调用的exit
是原始exit
的延续,因此值4
返回为原始调用的最终值(谜题)
(这就是DrRacket中真正发生的情况)
假设方案标准没有这样说。然后exit
现在实际上可能是newexit
。因此,调用它将导致无限循环。这不是DrRacket中发生的事情
事实上,如果我们用(lambda(v)(exit v))
替换退出
代码确实进入了无限循环
延续就像带值的跳转(a
GOTO
)。当我们有一些代码时,比如。。。。。。(foo)…
使用正常函数foo
,当对foo
的求值结束时,返回值将根据那里写的内容在该代码中进一步使用
当puzzle
用作foo
时,评估将进行相同的操作。Scheme试图找出puzzle
的返回值,以便在周围的代码中进一步使用它
但是puzzle
会立即调用call/cc
,所以它会创建这个标记,一个GOTO
标签,这样当/if/deep-insidepuzzle
调用(退出42)
时,控件会跳到-转到-那个标记,那个标签,并且42
被用作返回值
因此,当深入(谜题)
调用(42号出口)
时,其效果与调用(谜题)
刚刚返回42
作为其周围代码的返回值一样,而没有遍历谜题
中的所有剩余代码
这就是延续的工作原理。continuation是要跳转到的标记,带有一个值,将在后续代码中使用,就像前一段代码正常返回一样
使用Racket或等效宏,代码可能更容易阅读:
(define-syntax with-current-continuation ; let/cc
(syntax-rules ()
((_ c a b ...)
(call/cc (lambda (c) a b ...)))))
(define (puzzle2)
(let/cc exit ; --->>--------------->>------------>>-------------.
(define (local e) ; |
(let/cc local-exit ; --->>----------------------------. |
(exit (print+sub e ; | |
(let/cc new-exit ; -->>----. | |
(set! exit new-exit) ; | | |
(local-exit #f)) ; | | |
;; --<<-----* | |
))) ; | |
;; --<<-----------------<<--------* |
) ; |
(local 6) ; |
(exit 2)) ; |
;; --<<---------------<<------------------<<-----------*
)
(使用当前延续定义语法;let/cc
(语法规则()
((uuC a b…)
(呼叫/抄送(lambda(c)a b…)))
(定义(2)
(让/cc退出;-->-->----------------------->--------------。
(b)定义(本地e)|
(让/cc本地退出;-->-----------------------------------|
(退出(打印+子e;||
(让/抄送新出口;-->-----)|
(设置!退出新的退出);||
(define-syntax with-current-continuation ; let/cc
(syntax-rules ()
((_ c a b ...)
(call/cc (lambda (c) a b ...)))))
(define (puzzle2)
(let/cc exit ; --->>--------------->>------------>>-------------.
(define (local e) ; |
(let/cc local-exit ; --->>----------------------------. |
(exit (print+sub e ; | |
(let/cc new-exit ; -->>----. | |
(set! exit new-exit) ; | | |
(local-exit #f)) ; | | |
;; --<<-----* | |
))) ; | |
;; --<<-----------------<<--------* |
) ; |
(local 6) ; |
(exit 2)) ; |
;; --<<---------------<<------------------<<-----------*
)