$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
From: Giovanni P. Deretta (gpderetta_at_[hidden])
Date: 2006-05-06 09:55:49
Xi Wang wrote:
> On 5/6/06, Giovanni P. Deretta <gpderetta_at_[hidden] > wrote:
> 
>> Xi Wang wrote:
>>> It looks great.
>>> I still don't catch why there must be a "self" as the first parameter
>>> in a coroutine definition. Could the "current" function help here?
>>> If removed, the "yield" function would be much easier to use.
>>>
>> The coroutine return type is statically defined. Thus yield (that is
>> both a form of return and call) must know the return type and the
>> argument types. The only way to statically type check these types is to
>> make yield a member of the coroutine or take the coroutine as a
>> parameter. In both cases the coroutine needs a pointer to itself. A
>> current coroutine global pointer would necessarily erase all type
>> informations, and a global yield function that uses this pointer would
>> need to delay all checking until runtime. This would both slow down
>> yielding and prevent the compiler from checking error a compile time.
>> Note that in the "Other Issues" section there is a mention of a free
>> yield function that uses the current_coroutine pointer. But this
>> wouldn't be the prefered interface, if it is implemented at all.
> 
> 
> I see. A free yield may be not so bad, for most operating systems support
> retrieving current coroutine:-) Besides, what is the return type of "yield"?
> In the document I see
> tie(parm_1, parm_2,... parm_n) = self.*yield*(result_1, result_2,...
> result_n);
> I guess it should just return an integer or something else, not a real
> tuple, right?
> 
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).
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.
> 
>>> Another question is, does a "coroutine" object act as both a coroutine
>>> instance and a coroutine factory? According to the examples it seems
>>> that sometimes  a coroutine binds data when created, and sometimes
>>> a coroutine uses operator () to create a new instance, right?
>>>
>> Probably I should mark more clearly the pseudocode examples where I make
>> liberal use of the coroutine keyword both to create new instances and to
>> mark a coroutine body) and actual C++ code.
>>
>> In C++, a coroutine object *always* represent a single instance. The
>> constructor binds the coroutine with the function object that implement
>> the body. coroutine::operator(...) resumes (or start, if not already
>> started) the specific coroutine instance to which is applied.
> 
> 
> What confused me is that coroutine::operator(...) takes parameters.
> Does this mean the parameters of a coroutine can be bound at either
> creation time or invocation time or both? It seems that coroutine::operator
> ()
> mixes the responsibilities of both binding and resuming, which should be
> separated, in my opinion.
> 
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?.
-- Giovanni P. Deretta