Scheme 实施收益和发送方案

Scheme 实施收益和发送方案,scheme,coroutine,continuations,guile,delimited-continuations,Scheme,Coroutine,Continuations,Guile,Delimited Continuations,我试图将yield和yield从从Python移植到Scheme 以下是我完成的一个实现: (define (coroutine routine) (let ((current routine) (status 'new)) (lambda* (#:optional value) (let ((continuation-and-value (call/cc (lambda (return) (let ((returner

我试图将
yield
yield从
从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")
除其他事项外,每次需要重新启动协同程序时,我必须让一个新的
返回
变量,以便
退出协同程序。基本上,我觉得语法太冗长了。还有其他更简洁的语法吗

应该可以
产生
向协同程序发送
值。以下是必须如何使用协同程序的示例:

(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
被忽略,然后
100
1000
发送到生成器。我已经完成了一个基于@sylvester代码的实现,但是宏有问题:

(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)))
大概是这样的:

(定义(生成生成器程序)
(定义最后的返回值)
(定义最后一个值#f)
(定义(最后一个延续)
(let((结果(程序产量)))
(上次返回结果)
(定义(收益率值)
(电话/抄送(lambda)(续)
(设置!最后一个继续)
(设置!最后一个值)
(最后返回值)))
(λargs)
(呼叫/抄送(lambda(返回)
(设置!上次返回)
(如果(空?参数)
(最后一个延续最后一个值)
(应用最后一个延续参数(()())))
这样使用:

(定义测试
(制造发电机
(lambda(收款)
(收集1)
(收集5份)
(收集10份)
#f) ))
(测试);==>1.
(测试);==>5.
(测试);==>10
(测试);==>#f(程序完成)
现在,我们可以将内部构件包装到宏中:

(定义语法(定义协同程序stx)
(语法大小写stx()
((name.args.body)
#`(定义(name.args)
(制造发电机
(lambda(#,(基准->语法stx'yield))
.正文(()()))
注意,
definecoroutine
是使用语法case实现的,因为我们需要使
yield
不卫生

(定义协同程序(从n开始倒计时)
(let循环((n))
(如果(=n0)
0
(回路(-(产量n)1щщ))
(定义从10开始倒计时(从10开始倒计时))
(定义(忽略过程)
(lambda忽略)
(程序)
(映射(忽略从10开始的倒计时)'(1));=>(10 9 8 7 6 5)
;; 重置
(从10开始倒计时);=>9
(从10开始倒计时);=>8.
;; 重新设置
(从10到100倒计时);=>99

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


感谢@Sylvester给出了一个很好的答案

困难的部分是使发电机功能可用
产量
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))))]))

什么是收集?否则,它基本上就是我要寻找的。缺少一个功能,我将在问题中添加它。@amirouche make generator接受一个接受yield过程作为参数的过程,就像call/cc接受一个接受continuation过程作为参数的过程一样。因此,您可以在生成器中选择您想要调用的
yield
,因为您使用的参数名称是什么。我现在明白了。Guile在抱怨一个不推荐的功能。我添加了一个例子,说明我到底在寻找什么。谢谢,没关系!我将尝试自己使用移位重置来实现。什么是
coroutine映射
?在
zrange
的哪个位置可以得到参数?哪个参数
yield
不是zrange的参数。我认为它需要不卫生的宏。
coroutine map
对(zrange 0 10)返回的值进行迭代,直到出现错误。您的
coroutine map
如何知道它应该将
+
元素组合在一起?如果你想成倍增长呢?对于参数,我指的是
send
如果
zrange
的长度有限,是否可以向它发送更多的值?这是否像
按顺序在底部生成每个文件一样?当您
发送
某个文件时,生成器将重新启动,并
生成
“返回”发送的值。这就是为什么
(+n步进(产量n))
变成
(+01000)
。我只是想在我的实现中没有考虑map的第一个值。我将添加我已经完成的实现。谢谢。我认为shift/reset是替换call/cc的最佳方法?通常,对于使用guile分隔的continuation,提示是最好的方法,我怀疑您自己使用shift/reset的实现是否会比guile使用prompt和abort to prompt的调用有所改进。提示是guile实现异常以及转义继续的方式。我应该从guile的提示开始,看看您是否可以改进它们,但是对于这种用法(协同程序生成器),我怀疑您是否会改进。struct宏使用这种技巧的一种变体在全局环境中定义新名称,我也经常使用这种技巧。如果您有DrRacket,我建议您查看一些宏的源代码:它们编写得很好,您可以从Scheme或Racket文档中学习到很多技巧。看见
(define countdown
  (make-generator
   (λ ()
     (for ([n (range 5 0 -1)])
           (yield n)))))

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