gpt4 book ai didi

scheme - 调用 cc 示例 Racket

转载 作者:行者123 更新时间:2023-12-04 08:25:08 26 4
gpt4 key购买 nike

我正在分析有关使用 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)
函数 local使用参数 6 调用传递给 print+subx .但是值如何 2到达 print+suby ?
最重要的是,所有这些指令的执行顺序是什么?

最佳答案

调用 (puzzle)建立延续 exit这样调用 (exit val)就像那个电话(puzzle)一样刚刚返回的那个val值(value)。
然后打电话(local 6)制作。它设置了一个延续 local-exit这样调用 (local-exit val2)就像那个电话(local 6)一样刚刚返回的那个val2值(value)。当然,返回值被忽略,下一个调用,(exit 2)接下来会制作。
现在,设置后local-exit ,电话(exit (print+sub e ...))制作。它需要找出值 val3(print+sub e ...)首先,它可以将它传递给调用 (exit val3) .print+sub需要两个参数。该调用有两个必须计算的表达式,因此找到的值(如果有)将作为 x 传递。和 yprint+sub .
正在评估 e很简单。是6 .
评估第二个表达式,(call/cc (lambda (new-exit) ...)) , 设置另一个延续, new-exit , 这样调用 (new-exit y)相当于返回 y进入那个插槽 {y}在电话中等待 (print+sub 6 {y}) .
然后 body

      (lambda (new-exit)
(set! exit new-exit)
(local-exit #f))
被输入。 (set! exit new-exit)更改任何调用的含义 (exit val)从今往后一样 (new-exit val)改为调用。
现在,终于, (local-exit #f)叫做。它跳出 (local 6)打电话,立即返回 #f ,然后被忽略。来电 (exit 2)制作。就好像打电话 (new-exit 2)一样被制作。这意味着返回 2{y}插槽,所以调用 (print+sub e 2) (exit (print+sub e 2)) 现在执行。 print+sub打印它打印的内容并返回 4 ,所以 (exit 4)现在被称为。
现在关键的花絮是, exit 的值是多少?用在这里?是不是原版 exit延续,或改变的, new-exit ?
假设 Scheme 标准说在任何函数应用中 (foo a1 a2 ... an) foo首先评估,然后 ai s 以未指定的顺序计算,然后函数值应用于 n刚刚找到的参数值。这意味着这个 exit被称为原 exit继续,所以值 4作为原始调用的最终值返回 (puzzle) (这就是 DrRacket 中真正发生的事情)。
假设 Scheme 标准没有这么说。然后 exit实际上可能是 new-exit现在。因此调用它会导致无限循环。这不是 DrRacket 中发生的事情。
确实,如果我们替换 exit(lambda (v) (exit v)) ,
           ((lambda (v) (exit v))
(print+sub e
(call/cc
(lambda (new-exit)
(set! exit new-exit)
(local-exit #f))))))))
代码确实进入了无限循环。

延续就像带有值的跳转(a GOTO)。当我们有一些像 ...... (foo) ..... 这样的代码时具有正常功能 foo ,当评价 foo结束,根据那里写的内容,返回的值在该代码中进一步使用。
puzzle用作 foo ,评估过程相同。 Scheme试图找出 puzzle的返回值在周围的代码中进一步使用它。
但是 puzzle电话 call/cc立即,所以它创建了这个标记,一个 GOTO标签去,以便当/如果/深入内部 puzzle拨打 (exit 42) ,控件跳转到 - 转到 - 那个标记,那个标签,以及 42用作返回值。
所以当内心深处 (puzzle)一个电话 (exit 42) ,它与调用 (puzzle) 的效果相同。刚刚带着 42 回来了作为其周围代码的返回值,而无需遍历 puzzle 中的所有剩余代码.
这就是延续的工作方式。延续是要跳转到的标记,带有一个值,在后续代码中使用,就像由前面的代码段正常返回一样。

使用 Racket 的 let/cc 可以更容易地阅读代码。 ,或等效的宏:
(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 的右括号上放置了一个断点。形式。每个延续,如果被调用,直接跳转到它的定义 let/cc的结束括号,以便传递的值在后续计算中用作该表达式的返回值。基本上就是这样。
但令人费解的部分是,在 Scheme 中,您可以从该表单之外跳转到结束括号,从而重新进入旧的控制上下文。

关于scheme - 调用 cc 示例 Racket ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65305036/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com