yield
done plenty of times before, but I managed to reinvent it again tonight.A lot of the time when I've played with
call/cc
, I have approached it trying to recreate a form in a statement-oriented language, and so I would end up not caring about the argument I would pass to the invocation of the captured continuation (because all I cared about was reestablishing the control flow of the prior context; the prior context itself would just discard any value I passed to it). I think the usual interpretation of yield
is an instance of this in statement oriented languages.However, when I was playing with some code tonight, I saw that dummy value and said, "ugly! why is that there?" And I decided to see what would happen if I got rid of it.
This macro is the result. (Obviously you want a bit of sugar around it to non-hygenically bind
yield
, although there are cases where it is nice to bind a different name like visit
instead of yield
, depending on the domain.)
(define-syntax generator-via
(syntax-rules ()
;; puzzle 4 U: what role(s) does arg-list serve? (all occurrences matter)
((generator-via yield-id arg-list body ...)
(let ((yield-id #f) (get-next #f))
(lambda arg-list
(call-with-current-continuation
(lambda (exit)
(cond (get-next (get-next exit . arg-list))
(else (set! yield-id (lambda results
(call-with-current-continuation
(lambda (next)
(set! get-next
(lambda (new-exit . arg-list)
(set! exit new-exit)
(next . arg-list)))
(apply exit results)))))
(call-with-values (lambda () body ...)
;; puzzle 4 U: why below eta-expansion required?
(lambda args (apply exit args))))))))))))
Its got some fun behaviors. Consider:
> (define grows
(generator-via yield (x)
(let loop ((i 0))
(loop (+ i x (yield i))))))
> (grows 1)
0
> (grows 0)
1
> (grows 0)
2
> (grows 0)
3
> (grows 1)
5
> (grows 0)
6
(yes I just realized I could/should have
let
-bound the yield-id
itself. The other set!
invocations are believed to be necessary, barring tricks mixing letrec
and call/cc
.)
No comments:
Post a Comment