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