continue in catch block? seperate interaction with widgets and work flow

Daniel Keep daniel.keep.lists at gmail.com
Thu Oct 19 08:05:13 PDT 2006



Bill Baxter wrote:

> sounds kind of similar to generators & coroutines for which the keyword
> "yield" is often used.  "Green threads" is another related term.  You
> yield back to some controlling process but your functions state is
> preserved and you can get back there later.  Also I think I've seen it
> described somewhere as generalizing the function call stack into a
> "function call tree".
> 
> Some people seem to be crazy about that stuff lately, but I haven't yet
> really been able to grok what is so great about it.  It's supposed to
> simplify things, but all the I've seen just look harder to understand
> than the "regular way" to me.  Well the generators used to implement
> iterators in Python make sense to me.
> 
> Supposedly it's very good for games somehow, though:
> http://www.eve-online.com/faq/faq_07.asp (look for "stackless")
> http://harkal.sylphis3d.com/2005/08/10/multithreaded-game-scripting-with-stackless-python/
> 
> 
> --bb

Disclaimer: Big fan of both Python, Stackless, and this sort of stuff in
general.  May be just a *wee* bit biased :P

*ahem*

There are many ways in which coroutines / green threads / generators /
tasklets / etc. can make life easier.  Here's a few I can think of off
the top of my head:

1. State machines.

State machines are where you have something like this:

void update()
{
    switch( this.currentState )
    {
        case STATE_1:
            doOneThing();
            currentState = STATE_2;
            break;
        case STATE_2:
            doAnotherThing();
            currentState = STATE_3;
            break;
        case STATE_3:
            if( timeSpentWaiting >= 1*second )
                currentState = STATE_4;
            break;
        case STATE_4:
            doOneLastThing();
            currentState = DEAD;
            break;
    }
}

This is a pattern used in games, and any sort of incremental processing.
 The reason you do this is that you're trying to describe a process, but
you need to break off and do other things at the same time.  For
example, if you're simulating something and you have a million of these
buggers, you can't exactly run each one to completion all in one go.

However, with coroutines, the above becomes this:

void update()
{
    doOneThing();
    doAnotherThing();
    wait( 1*second );
    doOneLastThing();
    die();
}

Because you can suspend execution of the function, and then resume it
later, writing state machines becomes trivial.

2. Pipelines

Imagine you have a pipeline of some description.  Maybe it's an image
processing chain where each part of the pipeline streams pixels in from
the previous one, modifies them, and then streams them out to the next
in the pipeline.

There are a few ways you can do this.  You can have each component as a
separate function, with an overseeing controller:

{
    foreach( x, y, pixel ; src_image )
    {
        foreach( p ; processors )
            pixel = p(pixel);
        dst_image[x,y] = pixel;
    }
}

But then you have a problem with maintaining state with your processors.
 You could have each processor explicitly pull data from the previous
one, or explicitly push data to the next one, but then you're forced to
add control logic to the processors where it, frankly, doesn't belong.

With coroutines and channels, you can write the components like so:

void invert(Channel input_channel, Channel output_channel)
{
    while( input_channel.available
        && input_channel.connected
        && output_channel.connected )
    {
        auto pixel = input_channel.receive();
        pixel.r = 1.0 - pixel.r;
        pixel.g = 1.0 - pixel.g;
        pixel.b = 1.0 - pixel.b;
        output_channel.send(pixel);
    }
}

Which functions pretty much exactly like UNIX pipes.  The entire
pipeline is just wired together out of simple building blocks, and when
either the first runs out or the last breaks off early, the whole thing
folds up nice and neat.

Plus, the code is brain-dead simple :)

3. Concurrency

Another nice feature is that coroutines allow you to do multithreading
without ever having to worry about locks or synchronising.  Basically,
only one coroutine can be running at a time (on a single-core machine,
anyway).  SENDING a message to another coroutine sleeps the sender and
wakes the receiver, which gives you a simple, deterministic way to work
out how and when coroutines transfer control.

I've seen MUD servers written on coroutines that are mind-bogglingly
simple.  They LOOK like linear code, they work like linear code, but it
jumps around and acts like multithreaded code.  It's quite amazing to see :)

4. Generators

Finally, a somewhat mundane (although just as powerful) feature that
coroutines and their ilk give you are generators/iterators.  These are
the ones that use "yield"; they allow you to transfer control AND
information back to the caller.  I'm sure you can find many examples of
this easily enough; just go look at Python code.

*gets off soap-box*

Basically, coroutines are amazingly powerful; they let you write simple,
elegant code that looks like a dog's breakfast when written out to
accommodate "normal" function call semantics.

If you are serious about learning them, the best thing to do is to grab
Stackless Python, and play around with the MUD example program.  Write a
little program full of communicating agents.  Then go and try to write
it in D.  You'll be amazed at how constricted it feels to lose them :)

	-- Daniel

-- 
Unlike Knuth, I have neither proven or tried the above; it may not even
make sense.

v2sw5+8Yhw5ln4+5pr6OFPma8u6+7Lw4Tm6+7l6+7D
i28a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP  http://hackerkey.com/



More information about the Digitalmars-d mailing list