
I've been playing around with coroutines by trying to solve some "real" problems (in pseudo C++, without compilation). One thing that I noticed is that symmetry is important. For example, for a chess simulation: class move { ... }; class board { ... }; // Each player has his own instance of the board class void player(coroutine<move(move)> opponent, move his, board brd) { while( !brd.is_mate() ) { brd.opponent_move(his); move mine = ...; brd.my_move(mine); his = opponent(mine); } } void white(coroutine<move(move)> opponent) { board brd(WHITE); move mine = ...; brd.my_move(mine); his = opponent(mine); player(opponent, his, board); } void black(coroutine<move(move)> opponent, move his) { board brd(BLACK); player(opponent, his, brd); } void play() { coroutine<move(move)> b(black); white(b); } It's very convenient that "both sides" have the same type. It makes it easy to write the common player() routine. Another thing to note is that the coroutine function returns void. I found that to basically be always true -- I never needed to use the 'return' to pass back the last value. Like the next example shows, it's also very convenient to have stack unwinding as running the coroutine to completion is not always natural. In the example above, coroutine<move(move)> has a coroutine function with signature void(move). In some cases, it's even more convenient to have a void() signature as in the following example implementing a TCP server using blocking sockets. struct request { ... }; struct reply { ... }; bool exit = false; // set to 'true' by Ctrl+C handler void server(coroutine<reply(request)> c, unsigned short port) { socket ls; ls.listen(port); while( !exit ) { socket s = ls.accept(); while( !exit ) { request req = s.recv(); reply rep = c(req).get(); s.send(rep); if( rep == "logout" ) break; } } } void run() { coroutine<reply(request)> srv(std::bind(server, 2121)); request req = srv.get(); while ( !exit ) { reply rep = ...; if( srv(rep) ) req = srv.get(); } } Also, in the first example, coroutine::operator() returned T (like fn call) but this example required the use of get(). The function call way (op() returns T) is also convenient since it allows one to write code that works generically with coroutines or functions: template <typename C> void timer(C c, chrono::seconds interval) { while( interval != chrono::seconds::zero() ) { this_thread::sleep_for(interval); interval = c(interval); } } chrono::seconds tick(chrono::seconds last_interval) { std::cout << "tick" << std::endl; return last_interval; } // use with function timer(tick, chrono::seconds(1)); // use with coroutine coroutine<chrono::seconds(chrono::seconds)> c( [](coroutine<chrono::seconds(chrono::seconds)> c, chrono::seconds i) { timer(c, i); } ); chrono::seconds s = c(chrono::seconds(1)); s = c(2*s); s = c(3*s); s = c(5*s); I'm not sure what this means for the best interface but I did want to share these thoughts and examples. Regards, Eugene