[coroutine] new versions

I've uploaded two re-factored versions - I think most of the suggestions from the review are implemented (namespace is not corrected and documentation not updated yet). 1.) http://ok73.ok.funpic.de/boost-coroutine-self.zip 2.) http://ok73.ok.funpic.de/boost-coroutine-coro.zip Version 1) requires that coroutine-fn has only one argument == coroutine<>::self_t. Other arguments are accessed via coroutine<>::self_t::get< int >() and results via coroutine<>::get(). interface provides input/output iterators. typedef coroutine< int( int, int> > coro_t; int fn( coro_t::self_t & c) { int x = c.get< 0 >(); int y = c.get< 1 >(); c.yield( x +y); ... } coro_t coro( fn); int res = c( 3, 7).get(); Version 2) requires that coroutine-fn has only one argument too == coroutine<> with inverted signature. Other arguments are access via coroutine<>::get(). interface provides input/output iterators. typedef coroutine< int( int, int> > coro_t; int fn( coroutine< tuple< int, int >( int) & c) { int x = c.get().get< 0 >(); int y = c.get().get< 1 >(); c( x +y); ... } coro_t coro( fn); int res = c( 3, 7).get(); Both implementations are not optimized - I think we should get a small and clean interface first. Comments? regards, Oliver

I've uploaded two re-factored versions - I think most of the suggestions from the review are implemented (namespace is not corrected and documentation not updated yet).
1.) http://ok73.ok.funpic.de/boost-coroutine-self.zip 2.) http://ok73.ok.funpic.de/boost-coroutine-coro.zip
Version 1) requires that coroutine-fn has only one argument == coroutine<>::self_t. Other arguments are accessed via coroutine<>::self_t::get< int >() and results via coroutine<>::get(). interface provides input/output iterators.
typedef coroutine< int( int, int> > coro_t; int fn( coro_t::self_t & c) { int x = c.get< 0 >(); int y = c.get< 1 >(); c.yield( x +y); ... } coro_t coro( fn); int res = c( 3, 7).get();
Version 2) requires that coroutine-fn has only one argument too == coroutine<> with inverted signature. Other arguments are access via coroutine<>::get(). interface provides input/output iterators.
typedef coroutine< int( int, int> > coro_t; int fn( coroutine< tuple< int, int >( int) & c) { int x = c.get().get< 0 >(); int y = c.get().get< 1 >(); c( x +y); ... } coro_t coro( fn); int res = c( 3, 7).get();
Both implementations are not optimized - I think we should get a small and clean interface first. Comments? The single difference between both interfaces is that with fist the return is obtained using yield, while in the second is using the operator(). I prefer to use yield to return a result and the call operator to call to a coroutine. It is not enough clear to me what are
Le 05/10/12 17:22, Oliver Kowalke a écrit : the advantages of having a inversed coroutine as parameter. Anyway, maybe the use of the get accessor can be made more readable without effort using tags struct a{}; struct b{}; typedef coroutine< int( tagged<int, a>, tagged<int,b> > > coro_t; int fn( coro_t::self_t & c) { int x = c.get< a >(); int y = c.get< b >(); c.yield( x +y); ... } Best, Vicente

Am 05.10.2012 21:27, schrieb Vicente J. Botet Escriba:
The single difference between both interfaces is that with fist the return is obtained using yield, while in the second is using the operator().
the main difference between version 1 and 2 is that self_t is not a coroutine<> it is a different type. Version 2 was suggested by Giovanni and Eugene - especially Eugene has provided an example demonstrating the benefit of version 2: 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); }
I prefer to use yield to return a result and the call operator to call to a coroutine. because slef_t is not of type coroutine<> I expressed the context jump function as 'yield()' instead of operator()
It is not enough clear to me what are the advantages of having a inversed coroutine as parameter. If you use symmetric coroutines - see example above
Anyway, maybe the use of the get accessor can be made more readable without effort using tags
struct a{}; struct b{}; typedef coroutine< int( tagged<int, a>, tagged<int,b> > > coro_t; int fn( coro_t::self_t & c) { int x = c.get< a >(); int y = c.get< b >(); c.yield( x +y); ... }
does boost.tuple provide such a facility? regards, Oliver

On Fri, Oct 5, 2012 at 8:46 PM, Oliver Kowalke <oliver.kowalke@gmx.de>wrote:
[snip]
struct a{}; struct b{}; typedef coroutine< int( tagged<int, a>, tagged<int,b> > > coro_t; int fn( coro_t::self_t & c) { int x = c.get< a >(); int y = c.get< b >(); c.yield( x +y); ... }
Anyway, maybe the use of the get accessor can be made more readable without effort using tags does boost.tuple provide such a facility?
no, but boost.fusion does. Maybe coroutine should only support unary arguments/results and let the user choose its preferred "packaging" by explicitly specifying tuple or fusion map/sets? I do not have a strong opinion on this. -- gpd

On Oct 5, 2012, at 4:53 PM, Giovanni Piero Deretta <gpderetta@gmail.com> wrote:
On Fri, Oct 5, 2012 at 8:46 PM, Oliver Kowalke <oliver.kowalke@gmx.de>wrote:
Anyway, maybe the use of the get accessor can be made more readable without effort using tags does boost.tuple provide such a facility?
no, but boost.fusion does. Maybe coroutine should only support unary arguments/results and let the user choose its preferred "packaging" by explicitly specifying tuple or fusion map/sets?
+1 That would greatly simplify things. You wouldn't need to worry about signature inversion, the parameterization is streamlined, and accessing the argument can be reduced to an arg() accessor. ___ Rob

Am 06.10.2012 03:54, schrieb Rob Stewart:
On Oct 5, 2012, at 4:53 PM, Giovanni Piero Deretta <gpderetta@gmail.com> wrote:
On Fri, Oct 5, 2012 at 8:46 PM, Oliver Kowalke <oliver.kowalke@gmx.de>wrote:
Anyway, maybe the use of the get accessor can be made more readable without effort using tags does boost.tuple provide such a facility?
no, but boost.fusion does. Maybe coroutine should only support unary arguments/results and let the user choose its preferred "packaging" by explicitly specifying tuple or fusion map/sets?
+1
That would greatly simplify things. You wouldn't need to worry about signature inversion, the parameterization is streamlined, and accessing the argument can be reduced to an arg() accessor. coroutine<>::caller_t is a typedef of coroutine<> with inverted signature - you don't need to worry

Le 05/10/12 21:46, Oliver Kowalke a écrit :
Am 05.10.2012 21:27, schrieb Vicente J. Botet Escriba:
The single difference between both interfaces is that with fist the return is obtained using yield, while in the second is using the operator().
the main difference between version 1 and 2 is that self_t is not a coroutine<> it is a different type. I don't think the caller should be taken as a coroutine, as it could not be one. Version 2 was suggested by Giovanni and Eugene - especially Eugene has provided an example demonstrating the benefit of version 2:
I prefer to use yield to return a result and the call operator to call to a coroutine. because slef_t is not of type coroutine<> I expressed the context jump function as 'yield()' instead of operator()
It is not enough clear to me what are the advantages of having a inversed coroutine as parameter. If you use symmetric coroutines - see example above Symmetric coroutines are a different thing than your caller_t type, even if the caller is a coroutine. Symmetric coroutines lets the called coroutine to yield in place of the calling one. I don't see how this inversion helps to achieve symmetric coroutines. I should be missing something basic. Of course, I'm completely for the addition of symmetric coroutines.
Anyway, maybe the use of the get accessor can be made more readable without effort using tags
struct a{}; struct b{}; typedef coroutine< int( tagged<int, a>, tagged<int,b> > > coro_t; int fn( coro_t::self_t & c) { int x = c.get< a >(); int y = c.get< b >(); c.yield( x +y); ... }
does boost.tuple provide such a facility?
If Boost.Tuple doesn't provides them, Boost.Fusion tuple provides them already. Best, Vicente

Am 06.10.2012 00:20, schrieb Vicente J. Botet Escriba:
Le 05/10/12 21:46, Oliver Kowalke a écrit :
Am 05.10.2012 21:27, schrieb Vicente J. Botet Escriba:
The single difference between both interfaces is that with fist the return is obtained using yield, while in the second is using the operator().
the main difference between version 1 and 2 is that self_t is not a coroutine<> it is a different type. I don't think the caller should be taken as a coroutine, as it could not be one.
it was one suggestion from Giovanni and Eugene and as Giovanni - I'm undecided which version would be better
Version 2 was suggested by Giovanni and Eugene - especially Eugene has provided an example demonstrating the benefit of version 2:
I prefer to use yield to return a result and the call operator to call to a coroutine. because slef_t is not of type coroutine<> I expressed the context jump function as 'yield()' instead of operator()
It is not enough clear to me what are the advantages of having a inversed coroutine as parameter. If you use symmetric coroutines - see example above Symmetric coroutines are a different thing than your caller_t type, even if the caller is a coroutine. Symmetric coroutines lets the called coroutine to yield in place of the calling one. I don't see how this inversion helps to achieve symmetric coroutines. I should be missing something basic. Of course, I'm completely for the addition of symmetric coroutines.
missunderstanding I'm mean 'symmetric' in the sense of symmetric signature - take a look at Eugene's examples I need some input which version should be used for further development Votes? best, Oliver

On Fri, Oct 5, 2012 at 5:20 PM, Vicente J. Botet Escriba <vicente.botet@wanadoo.fr> wrote:
Le 05/10/12 21:46, Oliver Kowalke a écrit :
Am 05.10.2012 21:27, schrieb Vicente J. Botet Escriba:
Anyway, maybe the use of the get accessor can be made more readable without effort using tags
struct a{}; struct b{}; typedef coroutine< int( tagged<int, a>, tagged<int,b> > > coro_t; int fn( coro_t::self_t & c) { int x = c.get< a >(); int y = c.get< b >(); c.yield( x +y); ... }
does boost.tuple provide such a facility?
If Boost.Tuple doesn't provides them, Boost.Fusion tuple provides them already.
Maybe if c.get() returns a tuple, boost::tie can be used to improve readability: int x, y; boost::tie(x, y) = c.get(); This doesn't cover the cases when the types are not default constructable but that's a minority of times. I'm going to need this weekend to study the new designs before providing more feedback. -Eugene

On Fri, Oct 5, 2012 at 11:20 PM, Vicente J. Botet Escriba < vicente.botet@wanadoo.fr> wrote:
Le 05/10/12 21:46, Oliver Kowalke a écrit :
Am 05.10.2012 21:27, schrieb Vicente J. Botet Escriba:
The single difference between both interfaces is that with fist the return is obtained using yield, while in the second is using the operator().
the main difference between version 1 and 2 is that self_t is not a coroutine<> it is a different type.
I don't think the caller should be taken as a coroutine, as it could not be one.
what do you mean? of course it is a coroutine. Yes, it might be the default context for this thread, but it is still a coroutine; there isn't much to gain to treat it specially. -- gpd

I apologize for delayed response. On Fri, Oct 5, 2012 at 10:22 AM, Oliver Kowalke <oliver.kowalke@gmx.de> wrote:
Version 1) requires that coroutine-fn has only one argument == coroutine<>::self_t. Other arguments are accessed via coroutine<>::self_t::get< int >() and results via coroutine<>::get(). interface provides input/output iterators.
typedef coroutine< int( int, int> > coro_t; int fn( coro_t::self_t & c) { int x = c.get< 0 >(); int y = c.get< 1 >(); c.yield( x +y); ... } coro_t coro( fn); int res = c( 3, 7).get();
I only like yield if there's a separate generator class. For general coroutines, I would prefer the second version. But I'm torn if it makes sense to have a separate generator class. It's just sugar but it might lower the learning curve.
Version 2) requires that coroutine-fn has only one argument too == coroutine<> with inverted signature. Other arguments are access via coroutine<>::get(). interface provides input/output iterators.
typedef coroutine< int( int, int> > coro_t; int fn( coroutine< tuple< int, int >( int) & c) { int x = c.get().get< 0 >(); int y = c.get().get< 1 >(); c( x +y); ... } coro_t coro( fn); int res = c( 3, 7).get();
Overall, I do like this interface. Like I mentioned before, I would like to see the coroutine-function with a void return type. Or have an option for that. Maybe the coroutine can support both by looking at the return type of the passed in function and adjusting accordingly. The only problem with that is it would make it harder to have a type deducing function, i.e. make_coroutine. As for output iterators, I see that it's currently only supported for coroutines with void return type and arity of 1. If a coroutine has a non-void return type, output iterator can still be defined, just ignore the return values. Maybe also support arbitrary arity by having the iterator's value_type be a tuple for arities larger than 1. Regards, -Eugene

I would like to see the coroutine-function with a void return type. Or have an option for that.
I've had such a version but I dropped it because for detecting the completeness of the coroutine would require an extra jump (return from last jump-function and calling return in the coroutine-fn). And this makes the implementation for iterators very painful: void fn( coroutine<void(int)> & c) { c( 1); c( 2); } The iterator would be required to be incremented three times. And std::distance would return 3 instead of 2.
As for output iterators, I see that it's currently only supported for coroutines with void return type and arity of 1. If a coroutine has a non-void return type, output iterator can still be defined, just ignore the return values.
I would keep the interface simple - introducing such an iterator would be a source of confusion (why is the return type ignored etc.). The users could still use such coroutines but without an iterator interface. regards, Oliver

-------- Original-Nachricht --------
Datum: Thu, 11 Oct 2012 08:20:38 +0200 Von: "Oliver Kowalke" <oliver.kowalke@gmx.de> An: boost@lists.boost.org, boost@lists.boost.org Betreff: Re: [boost] [coroutine] new versions
I would like to see the coroutine-function with a void return type. Or have an option for that.
I've had such a version but I dropped it because for detecting the completeness of the coroutine would require an extra jump (return from last jump-function and calling return in the coroutine-fn). And this makes the implementation for iterators very painful:
void fn( coroutine<void(int)> & c) { c( 1); c( 2); }
The iterator would be required to be incremented three times. And std::distance would return 3 instead of 2.
If coroutine-fn is required to return void - we could detect the last 'caller_t::operator()' from coroutine-fn if we require to test the coroutine after each call: typedef coroutine< int() > coro1_t; typedef coroutine< void(int) > coro2_t; void fn( coro2_t & c) { c( 1); c( 2); } coro1_t c( fn); while ( c) { c(); if ( ! c) break; int res = c.get(); } Oliver

On Thu, Oct 11, 2012 at 10:22 AM, Oliver Kowalke <oliver.kowalke@gmx.de>wrote:
Von: "Oliver Kowalke" <oliver.kowalke@gmx.de>
An: boost@lists.boost.org, boost@lists.boost.org> > I would
like to see the coroutine-function with a void return type. Or have an option for that.
I've had such a version but I dropped it because for detecting the completeness of the coroutine would require an extra jump (return from last jump-function and calling return in the coroutine-fn). And this makes the implementation for iterators very painful:
void fn( coroutine<void(int)> & c) { c( 1); c( 2); }
The iterator would be required to be incremented three times. And std::distance would return 3 instead of 2.
If coroutine-fn is required to return void - we could detect the last 'caller_t::operator()' from coroutine-fn if we require to test the coroutine after each call:
typedef coroutine< int() > coro1_t; typedef coroutine< void(int) > coro2_t;
void fn( coro2_t & c) { c( 1); c( 2); }
coro1_t c( fn); while ( c) { c(); if ( ! c) break; int res = c.get(); }
What I do is having operator bool () return true if the coroutine has not terminated and has data. Also, the coroutine-fn is called immediately when the coroutine is called, and not deferred to the first operator() call. This way you can stop iteration as soon as data is no longer generated, whith the following pattern: void fn( coro2_t & c) { c(1); c(2); } coro1_t c(fn); for(; c ; c()) { int ret = c.get(); } Of course such a loop won't work in the coroutine-fn itself as the coro2_t c won't have data yet. An initial call to c() must be done. But I guess that coroutines will be used more often for pulling data than for pushing, so the interface works better like this. -- gpd

What I do is having operator bool () return true if the coroutine has not terminated and has data.
How does this indicate that the next call to coroutine<>::operator() would not return a value?
Also, the coroutine-fn is called immediately when the coroutine is called, and not deferred to the first operator() call.
I don't get it - when will be the body of coroutine-fn entered? coro_t c( fn);//? or c(); //? In the current implementation the first call of coroutine<>::operator() enters the body of the coroutine-fn. example with some additional arguments: typedef coroutine< std::string(int) > coro1_t; typedef coroutine< int(std::string) > coro2_t; void fn( coro2_t & c){ int x = c.get(); c( textual_cast( x) ); x = c.get(); c( textual_cast( x) ); } coro1_t c( fn); c( 1); // fn() is entered here std::string str = c.get(); int i = 0; for(; c ; c( ++i)) { std::string s = c.get(); } I think your forr-loop would not work . After the second call to 'c( i++)' in the loop 'c.get()' returns a value ('two'). 'coroutine::operator bool()' will evaluate to true (because result is available). If the next time c( ++i) is called the code returns from the last 'c( textual_cast( x) );' in coroutine-fn and terminates fn() - no data is returned. Because the for-loop does not test 'coroutine::operator bool()' before 'c.get()' you get a fault. Oliver

On Thu, Oct 11, 2012 at 12:39 PM, Oliver Kowalke <oliver.kowalke@gmx.de>wrote:
What I do is having operator bool () return true if the coroutine has not terminated and has data.
How does this indicate that the next call to coroutine<>::operator() would not return a value?
It indicates that the *last* call did return a value,
Also, the coroutine-fn is called immediately when the coroutine is called, and not deferred to the first operator() call.
I don't get it - when will be the body of coroutine-fn entered?
coro_t c( fn);//? or c(); //?
The first option.
In the current implementation the first call of coroutine<>::operator() enters the body of the coroutine-fn.
example with some additional arguments:
typedef coroutine< std::string(int) > coro1_t; typedef coroutine< int(std::string) > coro2_t;
void fn( coro2_t & c){ int x = c.get(); c( textual_cast( x) ); x = c.get(); c( textual_cast( x) ); }
coro1_t c( fn); c( 1); // fn() is entered here std::string str = c.get();
int i = 0; for(; c ; c( ++i)) { std::string s = c.get(); }
I think your forr-loop would not work .
After the second call to 'c( i++)' in the loop 'c.get()' returns a value
('two'). 'coroutine::operator bool()' will evaluate to true (because result is available). If the next time c( ++i) is called the code returns from the last 'c( textual_cast( x) );' in coroutine-fn and terminates fn() - no data is returned. Because the for-loop does not test 'coroutine::operator bool()' before 'c.get()' you get a fault.
you do need to invoke the coroutine-fn immediately. I think that the interface works better this way. See for eaxmple https://github.com/gpderetta/Experiments/blob/bind/switch_test.cc, specifically: { std::vector<int> v = { 0,1,2,3,4,5,6,7,8,9,10 }; auto f = [=](continuation<void(int)> c) { return std::for_each(v.begin(), v.end(), std::move(c)); }; for(auto i = callcc(f) ; i ; i()) { int x = i.get(); assert(v[x] == x); } } Of couse it doesn't come for free; as I said, you now have the symmetric issue that in the coroutine-fn, the coroutine object doesn't have data till the first call to operator(). But again, I think that pull by default works better. -- gpd

Also, the coroutine-fn is called immediately when the coroutine is called, and not deferred to the first operator() call.
I don't get it - when will be the body of coroutine-fn entered?
coro_t c( fn);//? or c(); //?
The first option.
what if the coroutine-fn expects some arguments? coroutine< int( string) > c( fn); c( "abc"); This would indicate that you have to block (jump back) inside coroutine-fn if you try to access an argument. Oliver

On Thu, Oct 11, 2012 at 1:44 PM, Oliver Kowalke <oliver.kowalke@gmx.de>wrote:
Also, the coroutine-fn is called immediately when the coroutine is called, and not deferred to the first operator() call.
I don't get it - when will be the body of coroutine-fn entered?
coro_t c( fn);//? or c(); //?
The first option.
what if the coroutine-fn expects some arguments?
coroutine< int( string) > c( fn); c( "abc");
auto fn(coroutine<string(int)> c) { assert(!c.has_data()); c(10); assert(c.has_data() && c.get() == "abc"); return c; } coroutine< int( string) > c( fn); assert(c.has_data() && c.get() == 10); c( "abc"); assert(c.terminated()); -- gpd

auto fn(coroutine<string(int)> c) { assert(!c.has_data()); c(10); assert(c.has_data() && c.get() == "abc"); return c; }
coroutine< int( string) > c( fn); assert(c.has_data() && c.get() == 10); c( "abc"); assert(c.terminated());
as in one of the previous examples - your example does not work if the return value depends on the arguments passed to coroutine void fn( coroutine<string(int)> & c) { string s = c.get(); if ( s == "abc") c(10); else c( 20); ... } the question is: - does the first invocation of 'coroutine<>::operator()' enter the body of coroutine-fn (in the example above the first instruction is 'string s = c.get()') - or the first call to 'coroutine<>::operator()' might jump to somewhere in the body of coroutine-fn (in the example from Giovanni it returns from c(10)) Oliver

On Thu, Oct 11, 2012 at 2:43 PM, Oliver Kowalke <oliver.kowalke@gmx.de> wrote:
auto fn(coroutine<string(int)> c) { assert(!c.has_data()); c(10); assert(c.has_data() && c.get() == "abc"); return c; }
coroutine< int( string) > c( fn); assert(c.has_data() && c.get() == 10); c( "abc"); assert(c.terminated());
as in one of the previous examples - your example does not work if the return value depends on the arguments passed to coroutine
well, this is true only for the first yielded return. If the coroutine-fn must not yield a value without receiving one first, it will have to call c.yield() to yield without value. It is ugly, I know, it is yet another deparature from coroutines-as-functions model. Unfortunately there is no way out of this: either the caller or the calle must return the first message without seeing the other side's message. You could have two functions: make_push_coroutine() and make_pull_coroutine() (the names are terribly long, but they are just to give an idea) which allows the user to chose the preferred behaviour. Another option is allowing passing additional arbitrary parameters to the coroutine constructor, as per std::thread. The coroutine-fn may (or not) use these parameters as if they were the result of yielding. There shouldn't be any requirements that the paremeters match the coroutine signature though. As with std::thread and std::bind, and differently than operator(), the parameters are always passed by copy and need an explicit ref() to pass by reference. This prevent the dangling reference problem that was raised during review. The biggest reason for having these extra parameters is coroutine pipelining: { auto pass1 = make_coroutine([](continuation<void(int)> output) { for(int i = 0; i < 10; ++i) output(i); return output; }); auto pass2 = make_coroutine ([](coroutine<void(double)> output, coroutine<int()> input) { for(auto x: input) { output(x*2); } return output; }, std::move(pass1)); std::vector<double> ret = { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18 }; assert(std::equal(begin(pass2), end(pass2), ret.begin())); } You could of course acheive the same thing by explicitly calling std::bind, but the syntax become heavier. You can't unfortuantley rely on c++ [=] lambda capture because a coroutine is not copyable (and [&] doesn't really sound right). Note that from the point of view of the pass2 coroutine-fn, there isn't really much difference between the input and the output, they are just two arbitrary coroutines. -- gpd
participants (5)
-
Eugene Yakubovich
-
Giovanni Piero Deretta
-
Oliver Kowalke
-
Rob Stewart
-
Vicente J. Botet Escriba