Boost logo

Boost :

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

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk