[plt-scheme] Confusing continuation behavior (a question about)
Grant Rettke wrote:
> On Thu, Jul 17, 2008 at 6:36 PM, Anton van Straaten
> <anton at appsolutions.com> wrote:
>>> 1 -> 2 -> 3 -> 1 -> 2 -> 3 -> ...
>>>
>>> 1. (a-process)
>>> 2. (set! cond1 #t)
>>> 3. (a-process)
>>> 4. (set! cond2 #t)
>> Here's a clue: after line 3 invokes the continuation that re-enters 'impl',
>> what is the value of 'return'?
>
> Here are my thoughts. I am still studying continuations. Honestly, I
> have a hard time visualizing them
I don't think you should expect otherwise, they're pretty tricky.
> but from stepping through with the
> debugger I think I've got a visual of what is happening along with my
> rudimentary practice of converting simple functions to CPS style. Are
> these thoughts correct?
>
>
> The first time 'a-process' gets called at line 1, the the value bound
> to 'return' is a continuation that looks like this:
>
> (lambda (ignored)
> (begin
> (a-process)
> (set! cond1 #t)
> (a-process)
> rest of program...))
Close. The first call to a-process doesn't belong there, because you're
trying to represent the continuation of that call, which doesn't include
the call itself. Just removing that first call gives a reasonable
approximation:
(lambda (ignored)
(set! cond1 #t)
(a-process)
...rest of program...)
This is correct as long as we ignore the interaction with the REPL,
which is appropriate if the code is running as a module.
(In the original example, the return value of a-process gets displayed
by the REPL, i.e. "Condition 1 not met" in this case. But modeling that
here would complicate things unnecessarily.)
It might help to think about this if the top-level program was a more
functional expression, like this:
(let ((result1 (a-process)))
(set! cond1 #t)
(let ((result2 (a-process)))
...rest of program...))
Explicitly saving the results of the call to a-process makes it easier
to see the relationship to the continuation of the first call to
a-process, which now looks more like this:
(lambda (result1)
(set! cond1 #t)
(let ((result2 (a-process)))
...rest of program...))
...which of course is very similar to my first version above.
> The second time 'a-process' gets called at line 3, the value of
> 'return' is still the same as above because when impl was rebound,
> that was the value bound to return.
Correct!
> After the call at line 3, we
> really want return to be bound to this:
>
> (lambda (ignored)
> (begin
> ;;;;; (a-process)
> ;;;;; (set! cond1 #t)
> (a-process)
> rest of program...))
Correct, except that again, the second call to a-process doesn't belong
inside its own continuation.
> So the question is, when we pass control to impl that second time
> after binding cond1 to #t, how do we access the *that* continuation
> and bind it to return. I'm going to do some reading.
Here's a hint: on the second call, impl is still being invoked by
call/cc, which passes a continuation to it. There are a few important
questions here (in case I've over-hinted, you might want to think about
them one at a time instead of reading ahead):
1. What is that continuation? You probably don't need to write it out
exactly unless you want to, just figure out roughly where it's going to
"return" to.
2. Is it useful?
3. What happens to it, i.e. where does the value go after it's passed to
the continuation currently bound to impl?
Note that as with the top-level expressions, it might help to rely less
on sequences of expressions, since they can make things harder to think
about because their results are implicitly discarded. This makes for
sort of invisible gaps in the functional control flow, from our
perspective of modeling the program's behavior. An example of such an
expression is:
(let/cc resume-here (set! impl resume-here))
This leads to more questions:
4. What is being implicitly discarded by this expression, i.e. what is
the result of the expression?
5. Can you tie all this together to fix the bug? (Note that major
rearrangement of the program isn't needed.)
Anton