I took a day to tinker and see what I could manage to mock up Goroutines, standing on the shoulders of the stackless work.
Based on that, here's a reimagining of this sample in the style of Go:
producer: func [sentence [text!]] [
let out: make-chan
let tokens: split sentence space
go (func [] [
for-next t tokens [ ; FOR-EACH is not stackless (yet), so...
send-chan out t/1 ; ...FOR-EACH can't unwind when this blocks
]
close-chan out
])
return out
]
pattern_filter: func [in "channel" /pattern [text!]] [
pattern: default ["ing"]
print ["Searching for" mold pattern]
let out: make-chan
go (func [<local> token] [
while [token: receive-chan in] [
if find token pattern [
send-chan out token
]
]
close-chan out
print "Done with filtering!!"
])
return out
]
print_token: func [in] [
print ["I'm sink, i'll print tokens"]
let done: make-chan
go (func [<local> token] [
while [token: receive-chan in] [
print [token]
]
close-chan done
print "Done with printing!"
])
return done
]
let sentence: "Bob is running behind a fast moving car"
unfiltered: producer sentence
filtered: pattern_filter unfiltered
done: print_token filtered
receive-chan done
This replaces the idea of coroutines speaking to each other directly with the idea that they synchronize and communicate through "channels".
- The producer isn't itself a "coroutine" but an ordinary function that instantiates a goroutine, and returns a channel object that represents its stream of produced tokens.
- The filter also isn't a coroutine... again an ordinary function, but one that takes a channel as an input parameter...returns a filtered channel as an output. To get the filtered channel, it instantiates a goroutine whose job it is to take items from
in
and decide which ones to send toout
. - The printer once again is an ordinary function that creates a goroutine to dump out a channel that it receives.
You get the desired output:
Searching for "ing"
I'm sink, i'll print tokens
running
moving
Done with filtering!!
Done with printing!
This seems a much smarter path than trying to deal with a contortion of generators like "special yielding syntax"! Does it look clear?
Note: I didn't have to write it as functions that run goroutines and return channels... in fact, most examples you see in Go aren't styled this way. Instead the main code makes the channel and then calls go on a goroutine that is parameterized with the channels. But I thought it made responsibility clearer who was doing what, e.g. to have the producer both make the channel and close it--vs being given a pre-made channel to fill.