[DOC] Call/CC Re: [Q][Goodie] continuations (was: RE: Tail Call)

Scott A Crosby crosby at qwes.math.cmu.edu
Sun Feb 3 02:27:49 UTC 2002


On Sat, 2 Feb 2002, Rob Withers wrote:

> Hi Scott,
>
> It took me a little time to figure some of this out, and none of it was due
> to that aweful link you posted!  ;-)   When I search the web for this
> topic, most of what I get back is headache food.
>

Heh... That was just a teaser of some of the really fucked up stuff that's
done with continuations.


> The trick with doing callCC in Squeak, is that we don't have callCC:, like
> your foo example.  Instead we create a continuation around a block and not
> part of a #callCC: expression.   Below is a fixed version of your foo/bar:
> example.

Someone else mentioned this to me. I'm still learning 'the smalltalk way'. :)

>
> Another issue is with which Process we create or invoke a
> continuation.  Anthony's #run method sounds perfect for this.  I have

Yes, one of the things you need is the notion of a process (suspended
continuations) queue, to have a spot to stuff those spare continuations
until you invoke them later. See my yield/fork/exit  below

In the squeak case, you probably want tighter integration with the
existing process stuff. You may even want to allow direct access to the
process stuff; say, the ability to, once you've got your hands on a
continuation, just say 'continuation spawnProcessValue: 12' or
something.

> attached a ContinuationFuture changeset.  Here are some other things I
> learned in writting this.


>
> If you don't fork your call to the creation of a continuation, whatever you
> do afterwards will be executed when you invoke the continuation.  If you do
> an inspect, then each time you invoke the continuation, it will be
> reinspected.  If you happen to invoke the continuation, just after you have
> created it, then without invoking conditionally, you are in an infinite
> loop.  I decided to create continuations from a forked process, that takes
> a second block to do work on the value of the continuation.  I do this fork
> at timingPriority, so I know it gets created before I try to invoke it.

See the above integration, for stuff with new processes, or you can use
the fork/yield/exit methods I give and manage your own continuations as a
pseudo-process's yourself. :)


> If you don't fork your call to the invocation of the continuation, then the
> process you invoke it with is *gone*.  If this happens to be your UI
> Process, then you are stuck without a UI Process.  This is typically more

Heh. :)

Will it though? The continuation you get also will (eventually) return
into the UI process.

If there's no way to get an 'empty' continuation, then EVERYONE you make
must have been callCC'd from the one you start off with, which means that
it has a... err, suffix,  of the one you start with, which means all
of them must contain the UI process.

The UI probess might not be idempotent.. What happens if,

flag _ 10.
cont _ (save a continuation including the UI process)
   .. do random UI stuff ..

then, randomly, reinvoke cont. (which contains the UI process) trying to
time-travel back. :)

I bet thing would behave in strange and mysterious ways, depending on how
much of the UI process's state is in local variables in the continuation
and how much is in instvar's... If its all in instvar's, there's probably
very little problem.

So, you shouldn't lose the UI thread, no matter what you do, unless you
infinite-loop.

> fatal than the first case, especially if you start infitine looping.   I
> believe the priority you fork the invocation of a continuation is the
> priority that the continuation runs at. I default to highIOPriority.
>
> The key to getting thi sto work was you example of error handling of
> objects.  With this example, you bail the first time through the setup
> method (#errorIn:handle:), when the continuation returns nil, as it does
> the first time.  Here was that code:
>

In both cases, I do return upwards.. There's no way to avoid it. The UI
process never 'goes away'... An infinite loop can happen, but thats the
exact same as:
   `[true] whileTrue.'


>
> Here is a bare-bones SimpleContinuationFuture, that is inthe attached
> changeset.


Ewww! :)

What you want to do here is to use the fork/exit/yield methods I supplied.
Handle the continuations as pseudoprocesses yourself, that way you don't
have to put any trust in race-conditions on the processor scheduling.

Also, I'm unsure why you're using continuations at all.. Why not just
stuff the aBlock into an instvar 'savedBlock', and do 'savedBlock value'
in #value:?


>
> Object subclass: #SimpleContinuationFuture
>          instanceVariableNames: 'valueContinuation'
>          classVariableNames: ''
>          poolDictionaries: ''
>          category: 'Kernel-Methods'
>
> value: value
>
>          [valueContinuation value: value] forkAt: Processor highIOPriority.
>          Processor yield.
>
> whenResolved: aBlock
>
>          | valueResolution initial |
>          [initial := true.
>          valueResolution := [:cont | valueContinuation := cont. nil]
> valueWithCC.
>          initial
>                  ifTrue: [initial := false]
>                  ifFalse: [aBlock value: valueResolution]] forkAt:
> Processor timingPriority.
>
> example
>
>          | cont |
>          cont := SimpleContinuationFuture new
>                  whenResolved: [:each | each inspect].
>          cont value: 23.

I'm not following this well; the forks are making things confusing because
of whats going on... They shouldn't be necessary at all.

Also, the use of initial seems.... grotty. I think something similar to my
#fork is cleaner.

Processor>>fork
   ^[ :cont | self storeSuspended: [ :ignore | cont true ].
             false.
   ] callCC.

Which returns true/false based on whether you're the parent or not. Its
very cute, what I do is I dont' store the *actual* continuation #cont,
Instead, I wrap it inside a block.  Note that I am implementing the UNIX
fork, not a spawn-new-process.

Processor>>exit
   [ :cont | self nextSuspended value: nil]

Processor>>yield
   [ :cont | self storeSuspended cont
             self nextSuspended value: nil].

Which saves the current continuation, then pops a new one off to use.

--

I'm not sure what you're trying to do here, so I can't construct example
code, but using the Process type of fork and race conditions seems wrong.



> At 12:15 PM 2/1/2002, you wrote:

Who is 'you'? Is it me? Someone else?

> >On Fri, 1 Feb 2002, Withers, Robert wrote:
> >
>
> >In the other examples we've given, we're not trying to access that 'system
> >level' implicit continuation. We're making explicit continuations
> >ourselves as blocks we pass them explicitly as arguments. Thats why those
> >examples differ from callCC.
>
> Is there a better, faster way to do this ContinuationFuture?
>

Unknown. I don't know what its supposed to do..

Scott





More information about the Squeak-dev mailing list