I'm trying to port yield
and yield from
from Python to Scheme.
Here is an implementation I've done:
(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)))))
The problem, with this implementation is that the way it has to be called doesn't looks like Python's 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")
Among other things, each time I need to-restart the coroutine, I must let
a new return
variable to be able exit
the coroutine. Basically, I find the syntax too verbose. Is there another to have cleaner syntax?
It should be possible to yield
and send
values to the coroutine. Here is an example of how the coroutine must be used:
(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
In the above, 1
is ignored and then 100
, 1000
are send
to the generator. I've done an implementation, based on @sylwester code, but I have troubles with the macro:
(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)))
Kudos to @Sylwester for a great answer.
The difficult part is making
yield
available to the generator function.datum->syntax
creates a syntax object, and requires you to provide another syntax object from which the context for the new object is taken. In this case, we can use stx which has the same context as the function passed into the macro.If people find it helpful, I use a simpler version:
An example of its usage:
One approach is here. If you are using guile, you should use prompts (they are about two orders of magnitude faster than using full continuations with guile):
How to implement Python-style generator in Scheme (Racket or ChezScheme)?
Something like this:
Used like this:
Now we can wrap the internals into a macro:
Notice that
define-coroutine
is implemented using syntax-case since we need to makeyield
unhygienic.