develooper Front page | perl.perl6.language | Postings from May 2003

Re: Coroutines

Thread Previous | Thread Next
From:
Luke Palmer
Date:
May 22, 2003 18:19
Subject:
Re: Coroutines
Message ID:
ygc1xyqtq7t.fsf@babylonia.flatirons.org
Damian wrotes:
> Didn't we already have this discussion six months ago???
> The conclusion of which seemed to be:
> 
>          http://archive.develooper.com/perl6-language@perl.org/msg12518.html
> 
> However, on revisiting it, I think I have an even simpler, best-of-both-worlds 
> solution that's also more in line with A6...
> 
> ==============================================================================
> 
> A coroutine is declared with the C<coro> declarator:
> 
>      coro fibs (?$a = 0, ?$b = 1) {
>          loop {
>              yield $b;
>              ($a, $b) = ($b, $a+$b);
>          }
>      }
> 
> and is allowed to have zero or more C<yield> statements inside it.
> 
> A coroutine is an object of type C<Coro> (just as a subroutine is an object of 
> type C<Sub>). C<Coro> objects have the following methods:
> 
>      next()                 # resumes coroutine body until next C<yield>
> 
>      next_args(PARAM_LIST)  # resumes coroutine body until next C<yield>,
>                             # rebinds params to the args passed to
>                             # C<next_args>.
>                             # PARAM_LIST is the same as the parameter list
>                             # of the coroutine itself
> 
>      each()                 # returns a lazy array, each element of which
>                             # is computed on demand by the appropriate
>                             # number of resumptions of the coroutine body
> 
> A C<Coro> object is therefore a type of iterator.
> 
> Calling a coroutine using the normal subroutine call syntax is the same as 
> calling the C<next> or C<next_args> method on it:
> 
>      $next = fibs();           # same as: $next = &fibs.next()
> 
>      $next = fibs(10,11);      # same as: $next = &fibs.next_args(10,11)
> 
>      while ($next = fibs()) {
>          if ($next < 0) {
>              fibs() for 1..2;  # skip the next two values
>          }
>      }
> 
> 
> To create multiple independent coroutine interators using a single coroutine 
> definition, one can simply use an *anonymous* coroutine:
> 
>      sub fibs_iter (?$a = 0, ?$b = 1){
>          return coro (?$x = $a, ?$y = $b) {
>              loop {
>                  yield $b;
>                  ($a, $b) = ($b, $a+$b);
>              }
>          }
>      }
> 
>      # and later...
> 
>      $iter = fibs_iter(7,11);
> 
>      while ($next = $iter.next()) {
>          if ($next < 0) {
>              $iter.next() for 1..2;    # skip the next two values...
>              $iter = fibs_iter(11,7);  # and change iterator
>          }
>      }
>
> Additionally, class C<Coro> would have a C<clone> method:
> 
>      $iter1 = &fibs.clone;
>      $iter2 = &fibs.clone;
> 
> which would return a reference to a new anonymous C<Coro> with the same 
> implementation as (but distinct state from) the original C<Coro>.

Sweet.

> Either way, for iteration that implies:
> 
>      <$fh>           # Call $fh.next()     # IO objects are iterators too
>      <$iter>         # Call $iter.next()
>      coro()          # Call &coro.next()
>      <&coro>         # Call &coro.next()
>      <coro()>        # Call .next() on iterator returned by call to coro()
> 
> 
> Whilst, in a list context:
> 
>      <$fh>           # Call $fh.each()
>      <$iter>         # Call $iter.each()
>      coro()          # Call &coro.each()

Er, what if it wanted to yield a list?

>      <&coro>         # Call &coro.each()
>      <coro()>        # Call .each() on iterator returned by call to coro()
> 
> 
> So then:
> 
>      for <$fh> {...}    # Build and then iterate a lazy array (the elements
>                         # of which call back to the filehandle's input
>                         # retrieval coroutine)
> 
>      for <$iter> {...}  # Build and then iterate a lazy array (the elements
>                         # of which call back to the iterator's coroutine)
> 
>      for coro() {...}   # Build and then iterate a lazy array (the elements
>                         # of which call back to the C<&coro> coroutine)
> 
>      for <&coro> {...}  # Build and then iterate a lazy array (the elements
>                         # of which call back to the C<&coro> coroutine)
> 
>      for <coro()> {...} # Call coro(), then build and iterate a lazy
>                         # array (the elements of which call back to the
>                         # iterator returned by the call to C<coro()>)
>
> 
> ==============================================================================
> 
> This approach preserves the simple (and I suspect commonest) use of "monadic" 
> coroutines, but still allows a single coroutine to produce multiple iterators.
> It also minimizes additional syntax and semantics.

I like most of it.  There's one problem: recursion.

    coro count($n) {
        count($n - 1) if $n > 0;
        yield $n;
    }

The call to count() inside count() would either start a new coroutine,
or continue the old one -- neither of which are the desired behavior.
What you want in this case is just to call the sub as usual.  This can
be remedied like this:

    sub _count_impl($n) {
        _count_impl($n - 1) if $n > 0;
        yield $n;
    }

    coro count($n) {
        _count_impl($n);
    }

But that seems like an awful lot of work for something this common.
Hmm...

    coro count($n) {
        (sub { _($n - 1) if $n > 0;  yield $n }).($n);
    }

Cool, but, eew.

I'm not sure how to solve the recursion problem while appeasing those
who want implicit iteration.

Another detail:  is implicit iteration scoped or global?

Luke

Thread Previous | Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About