【问题标题】:Does call/cc in Scheme the same thing with yield in Python and JavaScript?Scheme 中的 call/cc 与 Python 和 JavaScript 中的 yield 是否相同?
【发布时间】:2017-11-14 20:12:27
【问题描述】:

Scheme 中的 call/cc 与 Python 和 JavaScript 中的 yield 是否相同?

我不清楚生成器。在我看来,yield 使语言能够轻松生成迭代器。但我不确定我是否正确。

Scheme 中的call/cc 是否与其他语言中的yield 相关?如果是这样,它们是一样的吗,或者有什么区别?

谢谢!

【问题讨论】:

  • 这个问题非常广泛,可能不适合 SO 格式。值得一提的是,yield 在表达能力上等同于 delimited 延续的受限形式。但是,延续比生成器更通用。

标签: scheme generator yield


【解决方案1】:

call/cc 是一种比生成器更通用的语言功能。因此,您可以使用call/cc 制作生成器,但您不能使用生成器制作call/cc

如果您有一个计算值并在其他地方使用这些值的程序,它基本上是一台步进机器。人们可能会将其视为一个程序,每个步骤都有一个功能,其余步骤有一个延续。因此:

(+ (* 3 4) (* 5 6))

可以解释为:

((lambda (k)
  (k* 3 4 (lambda (v34)
            (k* 5 6 (lambda (v56)
                      (k+ v34 v56 k)))))
 halt)

k 前缀只是表明它是原语的 CPS 版本。因此,他们将最后一个参数作为带有结果的函数调用。另请注意,在 Scheme 中未定义的评估顺序实际上是在此重写中选择的。用这种优美的语言call/cc 就是这样:

(define (kcall/cc kfn k)
  (kfn (lambda (value ignored-continuation)
         (k value))
       k))

所以当你这样做时:

(+ (* 3 4) (call/cc (lambda (exit) (* 5 (exit 6))))) 
; ==> 18

这发生在幕后:

((lambda (k)
  (k* 3 4 (lambda (v34)
            (kcall/cc (lambda (exit k)
                        (exit 6 (lambda (v6)
                                 (k* 5 v6 k)))
                      k))))
 halt)

通过使用替换,我们可以证明这实际上完全符合预期。由于调用了退出函数,因此永远不会调用原始延续,因此取消了计算。与call/cc 为我们提供这种似乎并不明显的延续相反,它在 CPS 中没有魔法。因此call/cc 的大部分魅力在于编译阶段。

(define (make-generator procedure)
  (define last-return values)
  (define last-value #f)
  (define (last-continuation _) 
    (let ((result (procedure yield))) 
      (last-return result)))

  (define (yield value)
    (call/cc (lambda (continuation)
               (set! last-continuation continuation)
               (set! last-value value)
               (last-return value))))

  (lambda args
    (call/cc (lambda (return)
               (set! last-return return)
               (if (null? args)
                   (last-continuation last-value)
                   (apply last-continuation args))))))

(define test 
 (make-generator
   (lambda (collect)
     (collect 1)
     (collect 5)
     (collect 10)
     #f)))

(test) ; ==> 1
(test) ; ==> 5
(test) ; ==> 10
(test) ; ==> #f (procedure finished)

可能make a macro to make the syntax more similar,但它只是在此之上的糖。

更多示例I love Matt Mights page 有很多关于如何使用延续的示例。

【讨论】:

  • 至少使用 chicken-4.9(我没有使用更新的版本)初始化“last-continuation”的 lambda 表达式必须在完成时调用“last-return”,否则在完成时过程的控制将返回到生成器的第一次调用的继续,而不是最后一次。捕获由“过程”引发的任何异常也是一个想法,然后返回一个 lambda,该 lambda 在“最后返回”环境中重新引发异常,以便从生成器的最后一次调用中出现异常,并且不是从一开始。
  • @ChrisVine 令人惊讶的是,我从来没有发现这一点,因为我的延续是分隔的,但是当我将它包装在 let 中时,我确实得到了一个循环。谢谢!
  • 是的,对于具有分隔延续的方案(诡计和球拍,可能还有其他),它们更容易用于此特定目的。当使用完整的延续(chicken、chez 和其他)时,您必须用手用第二个延续来划定延续,这样做很容易出错,就像这里一样。无论如何,我最终得到了我在单独答案中提到的代码。
  • 顺便说一句,我仍然认为您修改后的代码没有正确处理异常。更改您的测试代码以将 '(collect 10)' 替换为 '(raise 'x)' 并在对 '(test)' 的第一次调用周围放置一个 'guard' 块并感到惊讶。
【解决方案2】:

您可以使用 call/cc 实现生成器。这是一个例子:

coroutines.ss

它们的工作方式类似于 python 和 ECMAScript 生成器。

【讨论】:

    【解决方案3】:

    下面是定义生成器的代码:

    (define-syntax define-generator
      (lambda (x)
        (syntax-case x (lambda)
          ((stx name (lambda formals e0 e1 ...))
             (with-syntax ((yield (datum->syntax (syntax stx) 'yield)))
               (syntax (define name
                 (lambda formals
                   (let ((resume #f) (return #f))
                     (define yield
                       (lambda args
                         (call-with-current-continuation
                          (lambda (cont)
                            (set! resume cont)
                            (apply return args)))))
                     (lambda ()
                       (call-with-current-continuation
                        (lambda (cont)
                          (set! return cont)
                          (cond (resume (resume))
                          (else (let () e0 e1 ...)
                                (error 'name "unexpected return"))))))))))))
            ((stx (name . formals) e0 e1 ...)
              (syntax (stx name (lambda formals e0 e1 ...)))))))
    

    my blog 有使用生成器的示例。生成器使用call-with-current-continuation,其方式类似于Python 中的yield,但更为通用。

    【讨论】:

      猜你喜欢
      • 2011-02-16
      • 1970-01-01
      • 1970-01-01
      • 2018-01-27
      • 2011-12-22
      • 2011-07-15
      • 1970-01-01
      • 1970-01-01
      • 2011-08-25
      相关资源
      最近更新 更多