为什么这个生成器实现在 Racket 中不起作用?

Why this generator implementation does not work in Racket?

我正在尝试使用 call/cc 在球拍中实现生成器,目前我有这段代码

(define (foo2)
  (define (g abort)
    (define-syntax-rule
      (yield x)
      (call/cc (lambda (k)
                 (set! g k)
                 (abort x))))
    (yield 'foo)
    (yield 'bar)
    (yield 'tar)
    (abort 'end))
  (thunk (call/cc g)))

如果我在 REPL 中使用它,它似乎工作正常

foo.rkt> (define g (foo2))
foo.rkt> (g)
'foo
foo.rkt> (g)
'bar
foo.rkt> (g)
'tar
foo.rkt> (g)
'end
foo.rkt> (g)
'end
foo.rkt> 

但是如果我尝试在列表中调用 g,它就会挂起,我不知道发生了什么

foo.rkt> (define g (foo2))
foo.rkt> (list (g) (g) (g))
;; prompt is not released

这是怎么回事?我的生成器实现哪里错了?

什么是(list (g) (g) (g))?嗯,在 CPS 中基本上是这样的:

(g& (lambda (g1) ; continuation g1 
     (g& (lambda (g2)
          (g& (lambda (g3)
               (list& g1 g2 g3 halt))))

你正在替换 g&,但你们所有人 yield 共享相同的首字母 abort,即延续 g1,你们永远不会达到延续 g2 因为每次 abort 都叫你 运行 继续 g1。如果您单步执行,您会注意到这个延续得到了所有的收益值,但最终它调用了 abort。即使您删除了最后一个调用,它也始终会在您的表达式中调用第二个 (g)

有助于了解 call/cc& 是这样的:

(define (call/cc& f cc)
  (f (lambda (v ignored-cc) 
       (cc v)) 
     cc))

现在 g 在 CPS 中看起来像这样。请注意,abort 是其中的自由变量,即使您替换 g&

,它们也是相同的
(define (g& abort cont)
  (call/cc& (lambda (k cc)
              (set!& g& 
                     k 
                     (lambda (undefined) 
                       (abort 'foo cc))))
            (lambda (ignored1)
              (call/cc& (lambda (k cc)
                          (set!& g& 
                                 k 
                                 (lambda (undefined) 
                                   (abort 'bar cc))))
                        (lambda (ignored2)
                          (call/cc& (lambda (k cc)
                                      (set!& g& 
                                             k 
                                             (lambda (undefined) 
                                               (abort 'tar cc))))
                                    (lambda (ignored3)
                                      (abort 'end cont)))))))))

作为解决方法,我想您需要控制设置为 g 的内容。例如。代替 (set! g k) 可能是这样的:

(set! g (lambda (new-abort)
          (set! abort new-abort)
          (k 'ignored)))

然后列表表达式按预期工作。为什么它在 REPL 中起作用是由于 continuation prompts.