The generator function can now be used to send values to the generator.

For example,

  (define g (generator (yield (yield (yield 1)))))
  (list (g) (g 2) (g 3) (g 4) (g) (g))

evaluates to '(1 2 3 4 4 4).  This is something that Python does (as a
generator.send method), which might be useful for using generators as
co-routines, and it is actually easy to implement since sending values
is exactly what we get when the generator call is actually calling the
saved continuation.  So most of the change is dealing with the
technicalities of throwing an error when the generator is called with
some arguments, when that's done after it's terminated (at the stage
where it's repeating the last value for ever).

A few tests added for this.

svn: r17979
This commit is contained in:
Eli Barzilay 2010-02-05 03:22:01 +00:00
parent 18c8e41fac
commit 505034ea26
2 changed files with 36 additions and 26 deletions

View File

@ -51,24 +51,33 @@
(define-syntax-rule (generator body0 body ...)
(let ()
(define (cont)
(define (yielder value)
(shift-at yield-tag k (set! cont k) value))
(reset-at yield-tag
(parameterize ([current-yielder yielder])
(call-with-values
(lambda () (begin body0 body ...))
;; only a normal return gets here
(case-lambda
;; Note: in this case, the generator was invoked with no
;; arguments, so returning no values is more symmetric. But
;; this is a common case, and probably people would expect a
;; void result more than no values.
[() (set! cont void)]
[(r) (set! cont (lambda () r))]
[rs (set! cont (lambda () (apply values rs)))]))
(cont))))
(define (generator) (cont))
(define cont
(case-lambda
[()
(define (yielder value)
(shift-at yield-tag k (set! cont k) value))
(reset-at yield-tag
(parameterize ([current-yielder yielder])
(define ret
(call-with-values
(lambda () (begin body0 body ...))
;; get here only on at the end of the generator
(case-lambda
;; Note: in this case, the generator was invoked with no
;; arguments, so returning no values is more symmetric.
;; But this is a common case, and probably people would
;; expect a void result more than no values.
[() void]
[(r) (lambda () r)]
[rs (lambda () (apply values rs))])))
(set! cont (case-lambda
[() (ret)]
[_ (error 'generator "cannot send values to a ~a"
"generator that has terminated")]))
(ret)))]
[_ (error 'generator
"cannot send a value to a generator before it starts")]))
(define (generator . xs) (apply cont xs))
generator))
(define-syntax-rule (infinite-generator body0 body ...)

View File

@ -235,15 +235,16 @@
(for/list ([x (in-generator (helper 0) (helper 1) (helper 2))])
x)))
(let ([g (lambda () (generator (yield 1) (yield 2) (yield 3)))])
(let ([g (g)]) (test '(1 2 3) list (g) (g) (g)))
(let ([g (g)]) (test '(1 2 3 10 10) list (g) (g) (g) (g 10) (g)))
(let ([g (generator (yield (yield (yield 1))))])
(test '(1 2 3 4 4 4) list (g) (g 2) (g 3) (g 4) (g) (g))))
(let* ([helper (lambda (pred num)
(for ([i (in-range 0 3)])
(yield (pred (+ i num)))))]
[g1 (generator
(helper odd? 1)
(yield 'odd))]
[g2 (generator
(helper even? 1)
(yield 'even))])
(for ([i (in-range 0 3)]) (yield (pred (+ i num)))))]
[g1 (generator (helper odd? 1) (yield 'odd))]
[g2 (generator (helper even? 1) (yield 'even))])
(test '(#t #f #f #t #t #f odd even) 'yield-helper
(list (g1) (g2) (g1) (g2) (g1) (g2) (g1) (g2))))