
On 5/6/06, Giovanni P. Deretta <gpderetta@gmail.com> wrote:
No, yield returns an actual tuple. The element types of the tuple are the same types of the function arguments. For example:
tuple<a_t, b_t, c_t> some_function (corutine<tuple<a_t, b_t, c_t>(d_t, e_t, f_t)>& self, d_t d, e_t e, f_t f) { // 1 a_t a; b_t b; c_t c; while(true) { do_something_with(d, e, f); a = some_value; b = some_other_value; d = yet_some_other_value; tie(d, e, f) = self.yield (a, b, d); // 2 } }
int main() {
coroutine<corutine<tuple<a_t, b_t, c_t>(d_t, e_t, f_t)> my_coroutine(some_function);
a_t a; b_t b; c_t c; d_t d = ...; e_t e = ...; f_t f = ...;
tie(a, b, c) = my_coroutine(d, e, f); // 3 do_something with a, b, c, d, e, f; tie(a, b, c) = my_coroutine(d, e, f); // 4
}
The first time a coroutine is entered (from 3), execution start at the main entry point (1). Each tuple can have an arbitrary number of arguments and results (using tuples), but their type and number is fixed at compile time. At the yield point control is relinquished to the caller. The argument values of yield will be returned to the caller as if the coroutine function had returned. (Note that yield is special cased for the tuple case and one is not required to write self.yield(make_tuple(...))). Yield is *not* the same thing as return because the current scope is not exited, stack is not unwound and thus scoped objects are not destroyed. This allows us to reenter the scope.
Next time the coroutine is invoked (note that for the caller there is *no* syntax distinction from first time invocation and successive ones), control is resumed at the yield point. Yield gives to the coroutine the values passed as parameter by the caller. As the parameter types and numbers are the same of the main entry point (i.e. the function signature), yield must know it (or be checked at run time).
Got it. Thanks a lot for the detailed explanation :-) So the return value (tuple) of a call to a yield in the coroutine body is the parameters of the subsequent invocation to the coroutine object, right? I was thinking it would return nothing. I'm a strong believer of static type checking and I think that
coroutines should be statically checked. Of course I see the usefulness of postponing checks until runtime. a coroutine<boost::any(boost::any)> would do it, and the library could have special support for it. But the preferred interface would be statically checked.
If the point is to check the return type, maybe we can trick the compiler like this: int __yield(T); #define yield(val) if (__yield(val) < 0) return val; If the return value of __yield should not be less than 0, the compiler would check the type of "val" automatically.
Apart for my general dislike for macros (except when you can't really do without :) ), this wouldn't work because return would exit the scope and cause all locals to be destroyed.
It is possible to implement a kind of pseudo-coroutines using a variation of the duff device (see http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html), where the yield is actually substituted by return, but they are severely limited. All your locals become static, thus are thread unsafe; it uses unsafe macros; the syntax is not natural as it requires special macros; and most importantly you can't have multiple instances of the same coroutine.
I dislike macros, too:-) The "return" I mentioned was just to guide the compiler to check the return type rather than to exit the function (of course it should not). More clearly, suppose real_yield is a global yield function, not type-safe. #define yield(x) \ if (FALSE) return x; \ real_yield(x); This macro would help to check the return type, and "return x" is never called. However, in this case, the return value (tuple) of real_yield could not be retrieved. So just forget it:-)
coroutine::operator() always invokes a coroutine. Binding is done only at creation time (in the constructor). I thought the article was clear. If you could point out where the confusion is, I will try to make it more clear.
Also consider that in pseudocode 'coroutine' is not an object but a keyword that introduces (possibly lambda) a coroutine body. In the pseudocode every 'coroutine' body is a diferent instance. Should I make this more clear?.
I got it. Thanks.