0

I have this code working code based on an example from wikipedia

(define (foo5)
  (define (control-state return)
    (define-syntax-rule
      (yield x) 
      (set! return (call/cc
                    (lambda (resume-here)
                      (set! control-state resume-here)
                      (return x)))))
    (yield 'foo)
    (yield 'bar)
    (yield 'tar)
    (return 'end))
  (thunk (call/cc control-state)))

I would never achieve this code by my self because the use of set! goes against every intuition there exists inside me.

First, control-state is local to foo5, okay, then I do (define g (foo5)) in the top level, at this point, in my head g points to the same reference that control-state points, a closure somewhere in the memory.

Then I call g as (g) it evaluates up to (set! control-state resume-here) at this point is where my intuition breaks. In my head this will set the internal control-state symbol to resume-here, BUT IT CHANGES THE OUTER g ALSO?. How this is even possible?

geckos
  • 5,687
  • 1
  • 41
  • 53
  • The big leap, I think, is that `g` is *not* `control-state`, it is a suspended continuation "inside" `foo5`. – molbdnilo May 06 '22 at 12:13
  • I will try to wrap my head around that – geckos May 06 '22 at 12:53
  • I'm still trying to work out the details of this one. Essentially, I consider `g` as "what remains of `foo5`", and the "what remains" part is updated in `yield`, before returning the value. Continuations can seriously mess with your head, so I might be completely wrong on this. – molbdnilo May 06 '22 at 13:12

1 Answers1

2

This is simpler than it looks.

First of all let's rewrite it, giving it a more obvious name and replacing the local macro with a function, to remove any confusion that a macro might be needed:

(define (yielder)
  (define (control-state return)
    (define (yield x)
      (set! return (call/cc (λ (resume-here)
                              (set! control-state resume-here)
                              (return x)))))
    (yield 1)
    (yield 2)
    (yield 3)
    (return 'end))
  (thunk (call/cc control-state)))

OK, so first of all notice that the top-level thing is (define (yielder) ...), and not (define yielder ...), so yielder is a function which, when called will return (thunk ...): a function of no arguments. That means that this:

(define g (yielder)

causes g to be bound to that function of no arguments. In particular g is not the same thing as control-state, and neither is it a suspended continuation. Nothing has happened yet as the function has not yet been called. Further g is never changed by any assignment: the control-state binding it closes over is mutated, but the binding of g itself is nt.

So, when g is called then it immediately calls the current value of control-state with the current continuation: a function which, when called, will immediately return from the call/cc and hence from g: that function is bound to return inside control-state.

control-state then calls (yield 1) which starts to evaluate

(set! return (call/cc (λ (resume-here)
                        (set! control-state resume-here)
                        (return x)))))

So this then involves calling

(λ (resume-here)
  (set! control-state resume-here)
  (return x))

With resume-here bound to a continuation which, if invoked, will cause the value it's invoked with to be assigned to return. It stashes this continuation into control-state, and then invokes the current value of return with the value of x which is 1. return then returns from that value from g: the assignment is not (yet) completed.

The next time g is called it does the same thing, creating a new return continuation and calling the current value of control-state with it as argument. But that value is now the continuation that was stashed there on the last call. So this now returns from the call/cc that was suspended by the invokation of the old value of return and completes the assignment before carrying on ... to the next yield call which does the same dance again except this time returning 2 from g. And so on.

Essentially the two continuations perform this little interleaved dance where you're for each call of g you're making a new continuation which says how to return from that call, and then invoking a continuation which restarts the body, stashing a new continuation for the next step before returning from g.

This is fiddly to understand but not impossible if you pore over it a bit.

ignis volens
  • 7,040
  • 2
  • 12
  • So every time I do `(blabla (call/cc (lambda (k) ...)) foo`, `k` is the continuation of that `call/cc` if I dot not call it the expression is never continued, is this reasoning correct? `foo` for example will not be evaluated until I call `k` somewhere in the code?! – geckos May 06 '22 at 18:25
  • @geckos: if `blabla` is `begin` or something like it then yes. If it's some function then it depends on the order in which arguments are evaluated which is undefined. – ignis volens May 07 '22 at 08:49
  • I didn't know that the order of evaluation of arguments were undefined in scheme, I always though that it was left to right, thanks! – geckos May 07 '22 at 15:27