I don't see what problem domain hits solutions that are best done with them all the time.
I don't see what problem domain hits solutions that are best done with them all the time.
Further, "coroutine", like so many other programming terms, has suffered from a certain fuzziness. There's a reasonably precise term used by programming language researchers, which very few languages have. Things like Python's generators do not conform entirely to this definition, but you get people calling them coroutines anyhow, along with so many other things.
So, rather than try to justify the exact PL-research definition, let me try to explain the interesting thing about the more sloppy, broad definition. In a nutshell:
When you call a function, you pass in certain arguments. Then you start at the beginning, generating state or values in response to the input, before finally returning some more values. You then discard all the intermediate state the function generated. The idea here is that there are times when you may want to not discard the state, and/or you may want to be able to not start the function at the beginning. You may also want to pass fresh values into the middle of the function.
One of the most clear examples of where this is useful is when you create a pipeline of functions within your code. Using "functions" that can get more input as they go along, and generate output without discarding all their intermediate state, makes it very easy to compose functions together into a pipeline that only know they are in a pipeline. You can take input from a user, apply a "coroutine" that then parses their input into numbers, feed that into a "coroutine" that groups them together into a run-length delimited format, then feed that into a coroutine that writes the results to a file. Each of these routines holds on to a little bit of state, instead of discarding it. Each of these routines is "pluggable"; you can take the same pipeline, pop the "user input" bit off the front, and read from a file, or stream from the network, and the same for all the little bits. You get a composable system of functions that all know just barely enough to function (which is what makes composition easy), instead of a set of functions that knows "A ha, I'm going to get this sort of linked list, traverse it, do 'my thing', and then return a new sort of linked list", which doesn't work on network streams, or hash tables, etc. This also is an easy way to operate on a "stream" without having to have the whole stream loaded into memory, and is more cache/memory-bandwidth friendly because you're only traversing it once.
Of course, if you work at it enough, you can do this with conventional functions. But you're going to write a lot of boilerplate, you're going to get it wrong, and it probably still won't be as composable in the end. Or you'll end up manually re-implementing "coroutine"ish things yourself.
This is only one example, but it's one of the clearer ones where having things able to retain state without being forced into the strict call hierarchy that conventional function stacks require is obviously a good idea.
(Pendant note: I've tried to keep this comment very language agnostic. There's a world of difference between Haskell and Python here, for instance. I'm going for the essence of the pattern in general, not in a particular language.)
That should be "Pedant"
;)
What's python still missing these days?
A classic example is iterators. Its very natural to write an iterator like
for(i=0; i<10; i++){
yield i
}
But to program the same thing without coroutines you need to keep all the control flow state explicitly and write the logic inside out like a state machine: i = 0
int getNext(){
if(i >= 10){
return Done
}else{
temp = i
i = i + 1
return temp
}
}
This inversion of control gets worse the more complicated your control flow is. For example if you have a recursive structure iterate_tree(t){
if(t.isLeaf){
yield t.value
}else{
iterate_tree(t.left)
yield t.key
iterate_tree(t.right)
}
}
then you suddenly need to also keep a bunch of stacks as your control flow state instead of just a single integer. I wont even try to write it because its just that ugly!---------
One way to prevent the inversion of control is to use internal iterators, where you pass a code block to the iterator:
void iterate(callback){
for(i=0; i<10; i++){
callback(i)
}
}
This works just fine sometimes but there are limitations: you cant't break out of the iteration at the middle and you cant interweave to iterations in parallel. Another situation where you see this kind of limitation is Javascript. Using callbacks to do async IO is better than writing everything inside out with all state in global variables (like you would do in C) but its a PITA to do simple things like chaining async calls one after the other inside a loop For example, with coroutines you could do something looking like foreach(url in list){
var x = await getUrl(url)
console.log(x)
}
restOfTheCode();
while in regular JS you need to use recursive functions instead: function go(i){
if(i >= urls.length){
restOfTheCode();
}else{
getUrl(function(x){
print(x)
go(i+1)
})
}
}I find them useful when conjoining function A with produces a sequence of results and function B which consumes the sequence†. Without coroutines you either have to turn one of the functions inside out to use it as a callback or break into threads and pass data between them.
With coroutines you can leave the functions in a more readable form and keep your cache footprint small by consuming the result as soon as it is produced.
␄
† I don't mean that I call A or B for each element of the sequence. A and B may have internal state and fill produce or consume the whole sequence in a single call. They could be organized to have a create function, a context structure, a "do one" call, and a destroy function… but that is a lot of noise and a lot of places for someone to make a mistake using them. A coroutine is cleaner.
function echo(socket)
local remote,data = socket:read()
if remote == nil then -- no data
socket:close()
return
end
socket:write(remote,data)
return echo(socket) -- Tail call, this is a goto
end
Normally, such code would need to be run in a separate process or thread but that's too much overhead. You can use an event-based method (say, with poll() or epoll()) but then the code becomes inverted (or involves callbacks---which in this case isn't too bad, but imagine trying to implement, say, SMTP). With coroutines, we can still use an event-based mainloop, but still keep the imperative style of the original function above. Our event loop would look something like: function mainloop()
local events = select:events()
for i = 1 , #events do
local err,keepgoing = coroutine.resume(events[i].code,events[i].socket,events[i].remote)
if not keepgoing then
select:remove(events[i].socket)
events[i].socket:close()
end
end
return mainloop()
end
and we would need to change our function just a bit: function echo()
local socket,remote = coroutine.yield(true)
local data = socket:read()
if data == nil then
return false
end
socket:write(remote,data)
return echo()
end
Oh, and don't forget the coroutine to accept new connections: function echoconnection()
local socket = coroutine.yield(true)
local connection,remote = socket:accept()
select:insert(connection,{ read = true } ,{ code = echo,remote = remote })
return echoconnection()
end
You can, of course, rewrite it such that socket:read() does the call to coroutine.yield(), but here I wanted to make it more explicit (as well as a shorter example) to fully hide the implementation (this is based on a working implementation I have in about 224 lines of Lua, which also handles signals and hides the coroutine calls from the main functions---the first example in this post would run as-is in the fully-blown implementation).Or you can just look at the code: http://pastebin.com/ZBq5gSbS The coroutine stuff is there, just well hidden.
I will also link to a sample multiuser chat room (no security, very basic): http://pastebin.com/u6vSPc69
[1] https://github.com/spc476/lua-conmanorg
[2] It's a Lua interface to the TCC library, which is a C compiler-as-a-library. Not used in the same code I posted, but it is something I find immensely useful, but I would classify as "advanced."
[3] http://www.inf.puc-rio.br/~roberto/lpeg/
This is the Lua Parsing Expression Grammar library. If you are familiar with compiler tools, think lex and yacc (or flex and bison).
for example, in the matasano crypto challenge i needed to simulate a message protocol between various actors (and then break it). i could have written client and server as separate threaded entities, but it was simpler to just have them each be a coroutine.
think of them like thread, state and bi-directional queue, in one easy package.
another example is lazy sequences. you could start up a separate thread that generates the sequences, but then you need to talk to it, throttle it in some way, and close it down. using a generator (a simple co-routine) you can still have your code for generating the sequence in one clean place, but it's easier to call and control.
if you've ever thought "it would be kinda cool if threads were simple, so i could structure my code like..." then you probably would have appreciated a coroutine.
[update: blog post on the protocol bit - http://www.acooke.org/cute/UsingCorou0.html - although really it doesn't say much more than the above]
1. Imagine the control flow you actually want.
2. Manually perform some subset of a CPS transform around the parts that would block.
3. Write the result of 2 and check it in.
And then the workflow for updating it is a. Read the code.
b. Attempt to reconstruct a mental model of the originally-desired control flow from the mess.
c. goto 1.
If you have some simulation/game in which many entities need to interact, you can use coroutines and write each entity's routine(s) imperatively. For example, BulletML lets you do this by embedding a suspendable domain-specific language in your project. If you check out http://www.youtube.com/watch?v=uK_YTyhj7T4, every bullet that either fires other bullets or changes velocity has one or more functions attached to it, and they are all running concurrently. It might be even better to do this without using an extra language, though.