On Wed, May 04, 2005 at 10:43:22AM -0400, Aaron Sherman wrote:
> On Wed, 2005-05-04 at 10:07, Aaron Sherman wrote:
> > On Wed, 2005-05-04 at 09:47, Joshua Gatcomb wrote:
> > 
> > > So without asking for S17 in its entirety to be written, is it
> > > possible to get a synopsis of how p6 will do coroutines?
> > 
> > A coroutine is just a functional unit that can be re-started after a
> > previous return, so I would expect that in Perl, a coroutine would be
> > defined by the use of a variant of return

A co(operating) routine is similar to a sub(ordinate) routine.
They are both a contained unit of code that can be invoked.

A subroutine carries out its entire functionality completely
and then terminates before returning control back to the caller.

A coroutine can break its functionality into many chunks. After
completing a chunk, it returns control back to the caller
(or passes control to a different coroutine instead) without
terminating.  At some later point, the caller (or some other
coroutine) can resumes this coroutine and it will continue
on from where it left off.  From the point of view of this
coroutine, it just executed a "subroutine" call in the middle
of its execution.  When used to it full limit, each coroutine
treats the other(s) as a subroutine; each thinks it is the
master and can run its code as it pleases, and call the other
whenever and from wherever it likes.

This can be used for a large variety of functions.

The most common (and what people sometimes believe the
*only* usage) is as a generator - a coroutime which creates a
sequence of values as its "chunk" and always returns control
to its caller.  (This retains part of the subordinate aspect
of a subroutine.  While it has the ability to resume operation
from where it left off and so doesn't terminate as soon as it
has a partial result to pass on, it has the subordinate trait
of not caring who called it and not trying to exert any control
over which coroutine is next given control after completing a

The mirror image simple case is a consumer - which accepts a
sequence of values from another coroutine and processes them.
(From the viewpoint of a generator coroutine, the mainline
that invokes it acts is a consumer coroutine.)

A read handle and a write handle are generator and consumer data
contructs - they aren't coroutines because they don't have any
code that "thinks" it has just called a "subroutine" to get
the next data to write (or to process the previous data that
was read).  However, read and write coroutines are perfectly
reasonable - a macro processor is a generator coroutine that
uses an input file rather than a mathematical sequence as
one facet of how it decides the next chunk to be generated
and passed on; a code generator is a consumer coroutine that
accepts parsed chunks and creates (and writes to a file perhaps)
code from it.

Controlling the flow of control is powerful - a bunch of
coroutines can be set up to act like a Unix pipeline.  The first
coroutine will read and process input.  Occassionally it
will determine that it has a useful chunk to pass on to its
successor, and pass control to that successor.  The niddle
coroutines on the chain will pass control to their predecessor
whenever they need new "input" and to their successor when
they have transformed that input into a unit of "output".
The last coroutine will accept its input and process it,
probably writing to the outside world in some way.

Coroutines can permit even wider range in the control flow.
Coroutines were used in Simula, which was a simulation and
modelling language, and it allowed independent coroutines to
each model a portion of the simulation and they would each be
resumed at appropriate times, sometimes by a master scheduler
which determined which major coroutine needed to be resumed
next, but also sometimes by each other.

A coroutine declaration should essentially declare 2
"subroutine" interfaces, describing the parameter and return
value information.  One is the function that gets called to
create a new instance of the coroutine; the other defines
the interface that is used to resume execution of an existing
instance of that coroutine.  The resume interface will look
like a definition of a subroutine - describing the argument
list and return values for the interface.

Having special purpose coroutine declarations for simple
generators and consumers would be possible and could hide the
need (in more general cases) for the full double interface.

The creation interface should (IMHO) return an object that can
be resumed (using the resumtion interface), could be tested
for various aspects of its state - .isalive (has it terminated
by returning from the function), .caller (which coroutine last
resumed it), probably others.

The act of resuming another coroutine is simply calling its
second interface with an appropriate set of arguments and
expecting that resumption to return an appropriate set of
values (when this coroutine is next resumed).  The resume
operation for a generator or consumer that doesn't want to
take any control of flow would simply resume the current
coroutine's caller.

Having said all that, there are changes to Aaron's Q&A
sequence that I'd want.

> Oh, I failed to comment on a few things (and given Luke's responses,
> this answer can be seen to dove-tail into what he said, though I don't
> think you need a coroutine trait most of the time).
> Here they are as questions with my expected default answers:
> Q: What does a coroutine return when exausted?
> A: It either explicitly "return"s something or falls off the end. This
> allows you to:
>       sub ten() { for 1..10 -> $_ { coreturn $_ } return undef; }
> correctly terminating ten() when called in a loop.
> If you ever call coreturn, then dropping off the end of the routine
> probably implicitly returns undef rather than the last statement
> executed as normal. Why? As a default way to signal the caller that
> we're done. More sophisticated means (adding traits to the undef) might
> be employed.

By having access to the coroutine object you can distinguish
whether a coroutine generator has been exhausted without
invoking it and checking the result for undef plus traits
sufficient to distinguish from a generated undef.  (Simple
generators would probably automatically be given an implicit
wrapper that returns an infinite series of undefs if resumed,
or perhaps raise an exception is resumed after the first undef
was returned.)

> Q: What happens if you change parameters?
> A: Nothing. You would have to store the information about what
> parameters were active somewhere, and no matter where you choose
> (current lexical pad, parameters, coroutine itself, etc.), there are
> many cases that are non-obvious to the programmer, and this gets
> strange:
>       while someco(@x) -> $_ {
>               while someco(@x) -> $_ {...}
>       }
> If you want to modify behavior based on parameter, return a closure that
> is a coroutine:
>       sub someco(@p) { return -> { for @p -> $_ { coreturn $_ } } }
>       my $co1 = someco(@x);
>       while $co1.() -> $_ {
>               my $co2 = someco(@x);
>               while $co2.() -> $_ {...}
>       }

I'd use "resume" instead of "coreturn" and the interface for
resume would allow values to be sent in as well as out.  (Using
an extended P5 rather than showing how lightly I've followed the
P6 details)

sub byn(@base) is coroutine($count,@add) {
    while(1) {
        return undef unless @base;
        ($count, @add) = resume .call (splice @base, 0, $count );

my $co = byn(1..10);
print $co.resume(3);            # 1 2 3
print $co.resume(2,50..56)      # 4 5
print $co.resume(10);           # 6 7 8 9 10 50 51 52 53 54
print $co.resume(10);           # 55 56
print $co.resume(10);           # (undef)
print $co.resume(10);           # exception or undef

(Some details are arbirtrary here and might be changed.
For example, I assumed that the implementation provides an
implicit resume assigning to ($count,@add) at the top of the
function before the first statement of code.)


Reply via email to