Version 19 of Coroutines for the Dazed and Confused

Updated 2010-06-02 18:46:05 by LVwikignoming

I hope these lies and oversimplifications will help others understand coroutines. See below for an increasingly accurate explanation. A special thanks to miguel on the #tcl freenode channel for taking the time to explain this to me.

proc allNumbers {} {
    yield
    set i 0
    while 1 {
        yield $i
        incr i 2
    }
}
coroutine nextNumber allNumbers
for {set i 0} {$i < 10} {incr i} {
    puts "received [nextNumber]"
}
rename nextNumber {}

The output of the above code is:

received 0
received 2
received 4
received 6
received 8
received 10
received 12
received 14
received 16
received 18

The first thing to understand is the yield command (which is in the coroutine's code). [yield $foo] acts like [return $foo], but it suspends execution; when execution is resumed, the proc restarts on the line after the previous call to yield.

The procedure "allNumbers" is a proc which first calls yield, and so suspends immediately (line 2); when it resumes it begins execution at line 3, after which it reaches another yield within a while loop on line 5. After producing each number, it yields its value ... and suspends until it is called again.

Now this line:

coroutine nextNumber allNumbers

This line will (a) run allNumbers until the first yield (b) create a command called 'nextNumber' that resumes execution of allNumbers when nextNumber is called.

So after this we have allNumbers suspended right before 'set i 0', and a new command 'nextNumber' that continues it where it left off.

Get it?


MS notes that he kind of lied to jblz: yield is a bit more complicated, but behaves exactly as described for this example.


ZB Wrong. It's the thing, that once confused me: the proc restarts not "on the line after previous call to yield", but resumes - one can say - directly in that lately left yield field, because there it can get ev. arguments "from outside". Consider the yield field as a "gate". You're leaving the proc - and getting back using this gate. Changed an example a bit - in my opinion, now it better explains the role of yield; changing theValue (and restarting the loop) one can see, how the increment step is different.

proc allNumbers {} {
    yield
    set i 0
    while 1 {
      set howMuch [yield $i]
      incr i $howMuch
    }
}

coroutine nextNumber allNumbers

set theValue 2
for {set i 0} {$i < 10} {incr i} {
    puts "received [nextNumber $theValue]"
}