control resumed inspired me to take a small exercise.
"Code a minimal approximation of the python yield operator based generators"
Here follow the results I managed to get with help from Matthias Felleisen, via the plt mailing list. You can see the non-edited exchange via the mailing list archives and control and macors. All control&prompt are forms specific to plt scheme's control.ss module. The first attempt looks something like this:
(require (lib "control.ss"))
(define (make-step)
(define (control-state)
(yield 1)
(yield 2)
(yield 3)
'finished)
(define (yield value) (control resume-here (set! control-state resume-here) value))
(define (generator) (prompt (control-state)))
generator)
(define step (make-step))
(step) (step)
It does the job. Kind of. For me the interesting bit is how the interplay of prompt and control achieve maintaining the current state of execution before the return and allowing to continue from that interruption. It can be done with call/cc but it looks a helluva a lot more complicated and is far more unreadable.
There is a problem with the code above though. As Matthias notes in his email:
LESSON: never 'test' by running things at the top-level prompt. It IS a prompt. -- Matthias
So comes version 2:
(require (lib "control.ss"))
(define (make-step)
(define (control-state)
(yield 1)
(yield 2)
(yield 3)
'finished)
(define (yield value)
(control resume-here
(set! control-state
(lambda () (prompt (resume-here))))
value))
(define (generator) (prompt (control-state)))
generator)
(define step (make-step))
(equal? '(1 2 3) (list (step) (step) (step)))
control-state is a variable holding a reference to a function with no arguments, doing what the python function would do. yield is a variable holding a reference to a function which does the interrupt magic. generator is a variable holding a reference to a (sugary) function which does the initial step.
The logic of the above algorithm is roughly as follows - mark thy state, do what you need to do, when you encounter control stop, remember your state by wrapping it in a function (resume-here), remember where do you what to continue from ( the set! control-state ...). Notice that the value of control-state is replaced. This is possible since the resume-here continuation is a function and a first class value.
So what next. Boilerplate code is tedious to write. And there is a lot of boilerplate code there. And every function, which needs to follow this recipe, will need to duplicate it.
So scheme macros to the rescue. It is not a trivial problem. A 'generator' function can look something like this: