使用延续时,`set!` 在 Racket 中如何工作?

How `set!` works in Racket when working with continuations?

我有基于维基百科示例的代码工作代码

(define (foo5)
  (define (control-state return)
    (define-syntax-rule
      (yield x) 
      (set! return (call/cc
                    (lambda (resume-here)
                      (set! control-state resume-here)
                      (return x)))))
    (yield 'foo)
    (yield 'bar)
    (yield 'tar)
    (return 'end))
  (thunk (call/cc control-state)))

我永远不会自己实现这个代码,因为 set! 的使用违背了我内心的每一个直觉。

首先,control-statefoo5的本地,好吧,然后我在顶层做(define g (foo5)),此时,在我的脑海中g指向control-state 指向的相同引用,内存中某处的闭包。

然后我调用 g 因为 (g) 它的计算结果高达 (set! control-state resume-here) 在这一点上我的直觉崩溃了。在我看来,这会将内部 control-state 符号设置为 resume-here,但它也会更改外部 g?这怎么可能?

这比看起来简单。

首先让我们重写它,给它一个更明显的名字并用一个函数替换本地宏,以消除可能需要宏的任何混淆:

(define (yielder)
  (define (control-state return)
    (define (yield x)
      (set! return (call/cc (λ (resume-here)
                              (set! control-state resume-here)
                              (return x)))))
    (yield 1)
    (yield 2)
    (yield 3)
    (return 'end))
  (thunk (call/cc control-state)))

好的,所以首先注意top-level的东西是(define (yielder) ...),而不是 (define yielder ...),所以yielder 是一个函数,调用时将 return (thunk ...):一个没有参数的函数。这意味着:

(define g (yielder)

导致 g 绑定到那个没有参数的函数。特别是 gcontrol-state 不是一回事,也不是挂起的延续。由于尚未调用该函数,因此尚未发生任何事情。此外 g 永远不会被任何赋值改变:它在 上关闭的 control-state 绑定是 突变的,但是 g 本身的绑定是 nt.

所以,当 g 调用时,它会立即调用 control-state 的当前值和当前的延续:一个函数,当被调用时,将立即从 call/ccg 中 return:该函数绑定到 control-state 内的 return

control-state 然后调用 (yield 1) 开始计算

(set! return (call/cc (λ (resume-here)
                        (set! control-state resume-here)
                        (return x)))))

所以这涉及调用

(λ (resume-here)
  (set! control-state resume-here)
  (return x))

resume-here 绑定到一个延续,如果调用它,将导致将其调用的值分配给 return。它将此延续存储到 control-state 中,然后使用 x 的值 1 调用 return 的当前值。 return 然后 return 来自 g 的值:分配尚未(尚未)完成。

下次调用 g 时,它会做同样的事情,创建一个新的 return 延续,并用它作为参数调用 control-state 的当前值。但该值现在是上次调用时藏在那里的延续。所以现在 return 来自 call/cccall/cc 被调用 return 的旧值暂停并在继续之前完成分配...到下一个 yield除了这次 return 从 g2 之外,调用再次进行相同的舞蹈。等等。

本质上,这两个延续执行这种交错的小舞蹈,每次调用 g 时,您都会创建一个新的延续,说明如何从该调用中 return,然后调用重新启动主体的延续,在 return 从 g 开始之前为下一步存储新的延续。

这很难理解,但如果你仔细研究一下,也不是不可能。