【问题标题】:Implement yield and send in Scheme实现 yield 和 send in Scheme
【发布时间】:2015-08-17 08:23:47
【问题描述】:

我正在尝试将 yieldyield from 从 Python 移植到 Scheme。

这是我完成的一个实现:

(define (coroutine routine)
  (let ((current routine)
    (status 'new))
    (lambda* (#:optional value)
      (let ((continuation-and-value
         (call/cc (lambda (return)
            (let ((returner
                   (lambda (value)
                 (call/cc (lambda (next)
                        (return (cons next value)))))))
              (if (equal? status 'new)
                  (begin
                (set! status 'running)
                (current returner))
                  (current (cons value returner)))
              (set! status 'dead))))))
    (if (pair? continuation-and-value)
        (begin (set! current (car continuation-and-value))
           (cdr continuation-and-value))
        continuation-and-value)))))

这个实现的问题是它的调用方式看起来不像 Python 的 yield

(define why (call/cc (lambda (yield)
               (format #t "love me or leave me!")
               (yield "I leave!")
               ;; the program never reach this part
               (format #t "it probably left :("))))
(format #t "return actually populates WHY variable\n")
(format #t "WHY: ~a\n")

除此之外,每次我需要重新启动协程时,我必须 let 一个新的return 变量才能exit 协程。基本上,我觉得语法太冗长了。有没有更简洁的语法?

应该可以将yield send 值传递给协程。以下是必须如何使用协程的示例:

(define-coroutine (zrange start step)
  "compute a range of values starting a START with STEP between
   each value. The coroutine must be restarted with 0 or more, which
   is added to the step"
  (let loop ((n start))
    (loop (+ n step (yield n)))))


(coroutine-map (zrange 0 10) '(1 100 1000 10000 100000))
;; => 0 110 1120 11130 111140

在上面,1 被忽略,然后1001000send 到生成器。我已经完成了一个基于@sylwester 代码的实现,但是我在使用宏时遇到了问题:

(define (make-generator procedure)
  (define last-return #f)
  (define last-value #f)
  (define last-continuation (lambda (_) (procedure yield)))

  (define (return value)
    (newline)(display "fuuu")(newline)
    (call/cc (lambda (continuation)
               (set! last-continuation continuation)
               (set! last-value value)
               (last-return value))))
  (lambda* (. rest)  ; ignore arguments
    (call/cc (lambda (yield)
               (set! last-return yield)
               (apply last-continuation rest)))))

(define-syntax define-coroutine
  (syntax-rules ()
    ((_ (name args ...) body ...)
     (define (name args ...)

       (make-generator
        (lambda (yield)
          body ...))))))

(define-coroutine (zrange start step)
  (let loop ((n start))
     (loop (+ n step (yield n)))))

(display (map (zrange 0 10) '(1 100 1000 10000 100000)))

【问题讨论】:

  • 什么是coroutine-mapzrange 你在哪里得到论据?
  • 哪个参数? yield 不是 zrange 的参数。我认为它需要不卫生的宏。
  • coroutine-map 遍历 (zrange 0 10) 返回的值,直到出现错误。
  • 你的coroutine-map 怎么知道+ 应该把元素放在一起?如果你想倍增怎么办?有参数我指的是send,如果它有一个有限的长度,你能向zrange发送更多的值吗?会不会像yielding一个个在底部依次排列?
  • 当您send 某事时,生成器重新启动并且yield“返回”发送的值。这就是为什么(+ n step (yield n)) 变成(+ 0 10 100)。我只是想在我的实现中没有考虑地图的第一个值。我将添加我已经完成的实现。

标签: scheme coroutine continuations guile delimited-continuations


【解决方案1】:

类似这样的:

(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)

现在我们可以将内部封装成一个宏:

(define-syntax (define-coroutine stx)
  (syntax-case stx ()
    ((_ (name . args) . body )
     #`(define (name . args)
         (make-generator 
          (lambda (#,(datum->syntax stx 'yield))
            . body))))))

注意define-coroutine 是使用语法大小写实现的,因为我们需要使yield 不卫生。

(define-coroutine (countdown-from n)
  (let loop ((n n))
    (if (= n 0)
        0
        (loop (- (yield n) 1)))))

(define countdown-from-10 (countdown-from 10))

(define (ignore procedure)
  (lambda ignore
    (procedure)))

(map (ignore countdown-from-10) '(1 1 1 1 1 1)) ; ==> (10 9 8 7 6 5)

;; reset
(countdown-from-10 10)  ; ==> 9
(countdown-from-10)     ; ==> 8
;; reset again
(countdown-from-10 100) ; ==> 99

【讨论】:

  • collect 是什么?否则,这基本上就是我要找的。​​span>
  • 缺少一个功能,我会在问题中添加它。
  • @amirouche make-generator 采用一个接受 yield-procedure 作为参数的过程,就像 call/cc 采用一个接受延续过程作为参数的过程一样。因此,您可以在生成器中选择要调用的 yield,因为您使用任何参数名称。
  • 我现在明白了。 Guile 抱怨一个已弃用的功能。我添加了一个我正在寻找的示例。
  • 谢谢,没关系!我会尝试自己使用 shift reset 来实现。
【解决方案2】:

这里有一种方法。如果您使用的是 guile,则应该使用提示(它们比使用 guile 的完整延续快两个数量级):

How to implement Python-style generator in Scheme (Racket or ChezScheme)?

【讨论】:

  • 谢谢。我认为 shift/reset 是替换 call/cc 的最佳方式?
  • 一般来说,对于使用 guile 的定界延续,提示是要走的路,我怀疑您自己使用 shift/reset 的实现将是对 guile 的 call-with-prompt 和 abort-to- 的改进迅速的。提示是 guile 实现异常以及转义延续的方式。我应该从 guile 的提示开始,看看你是否可以改进它们,但对于这种用法(协程生成器)我怀疑你会。
【解决方案3】:

感谢@Sylwester 的出色回答。

困难的部分是使yield 可用于生成器函数。 datum->syntax 创建一个语法对象,并要求您提供另一个语法对象,从中获取新对象的上下文。在这种情况下,我们可以使用与传入宏的函数具有相同上下文的 stx。

如果人们觉得它有帮助,我会使用更简单的版本:

(define-syntax (set-continuation! stx)
  "Simplifies the common continuation idiom
    (call/cc (λ (k) (set! name k) <do stuff>))"
  (syntax-case stx ()
    [(_ name . body)
     #`(call/cc (λ (k)
                  (set! name k)
                  . body))]))

(define-syntax (make-generator stx)
  "Creates a Python-like generator. 
   Functions passed in can use the `yield` keyword to return values 
   while temporarily suspending operation and returning to where they left off
   the next time they are called."
  (syntax-case stx ()
    [(_ fn)
     #`(let ((resume #f)
             (break #f))
         (define #,(datum->syntax stx 'yield)
           (λ (v)
             (set-continuation! resume
               (break v))))
         (λ ()
           (if resume
               (resume #f)
               (set-continuation! break
                 (fn)
                 'done))))]))

其用法示例:

(define countdown
  (make-generator
   (λ ()
     (for ([n (range 5 0 -1)])
           (yield n)))))

(countdown)
=> 5
...
(countdown)
=> 1
(countdown)
=> 'done
(countdown)
=> 'done

【讨论】:

  • struct 宏使用这个技巧的一个变体来定义全局环境中的新名称,我也经常使用它。如果你有 DrRacket,我建议你查看一些宏的源代码:它们写得很好,你可以从 Scheme 或 Racket 文档中学到很多不容易学习的技巧。见stackoverflow.com/questions/20931806/…
猜你喜欢
  • 1970-01-01
  • 2017-10-28
  • 2013-11-15
  • 2010-10-19
  • 1970-01-01
  • 1970-01-01
  • 2020-04-22
  • 2014-11-11
相关资源
最近更新 更多