Simple coroutines for Perl 6, inspired by the Lua coroutines.
Perl6
Switch branches/tags
Nothing to show
Latest commit 75ee4f6 Mar 10, 2017 @marcoonroad committed on GitHub Merge pull request #5 from zoffixznet/patch-1
Add mandatory "perl" META field
Permalink
Failed to load latest commit information.
lib/Coro
t
.gitignore
.travis.yml
LICENSE
META.info
README.md

README.md

Coro-Simple

Build Status

Simple coroutines for Perl 6, inspired by the Lua's coroutines.

This is a module for stackful asymmetric coroutines, which suspend their control flows with yield instead of shift the flow to another coroutine with transfer (these are called symmetric coroutines).

If you want to know more about coroutines, I suggest you to read this nice paper: http://www.inf.puc-rio.br/~roberto/docs/MCC15-04.pdf ...

Features and Issues

The coro / yield functions from this module are implemented using the gather / take built-in P6's functions, which has some interesting features:

  • It has a dynamic scope: it doesn't care about how many calls down are needed to find a take.

  • Is a list generator: useful for list processing with filters and transformers.

  • And also lazy: delaying the evaluation until you really need the result.

Some p6 programmers argue that the gather / take itself is like a coroutine. In fact, the lazy property of gather / take fulfills very well the definitions of Marlin’s doctoral thesis:

the values of data local to a coroutine persist between successive calls;

And:

the execution of a coroutine is suspended as control leaves it, only to carry on where it left off when control re-enters the coroutine at some later stage.

Based on the brief discussion above, the coro / yield also has some features:

  • The coroutine doesn't care about how many calls down are needed to find a yield, even inside many other nested function stacks.

  • The yield generates only one value per cycle, but you can yield an anonymous list to avoid it.

But there are some issues too:

  • I advise you to not use gather / take inside any coroutine, even I don't know what will happen.

  • It doesn't generate the last values with return (as is the case of Lua), so you must use yield again.

You can also yield "nothing" using the suspend function (and with none argument, just for a temporary shift of control). Don't worry about, it will return internally the True value (as a status that the coroutine is alive).

Description

Coroutine: Declaration

So, let's go see some examples.

First and foremost, you declare a coroutine with:

coro { ... }; # zero-arity coroutine

Or with:

coro -> $param1, $param2, $param3 { ... }; # 3-arity coroutine

Or even with:

coro -> @params {
    for @params -> $param { do-some-thing-with ($param) }
}
# variadic arguments through an array
Coroutine: Constructor

The coro keyword above gives back a constructor, and you may think "but why it returns a constructor?"... Well, for two main reasons:

  • For code reuse: you can use the coroutine on different places, without declare / return again it every time.

  • Recovering to a initial state: when the coroutine dies, you can just reassign it to the generator.

With Lua, if you want to reuse a coroutine, you will need explicitly return a coroutine that will reuse the given arguments as a closure:

function iter (xs)
  return coroutine.wrap (function ( )
    for _, x in ipairs (xs) do
      coroutine.yield (x)
    end
  end)
end

So, I decided implement a different approach...

Some example (an iterator function):

my &iter = coro -> $xs {
    for @$xs -> $x { yield $x }
}

The iter function above will receive an anonymous list and then give back a generator function... generator? Well-minded, now we will see generators.

Coroutine: Generator

Note: here, the generator definition is just for a function that returns the next value (every time that it's called), not as is usually called an asymmetric coroutine without dedicated stacks (which cares about if you will call yield out of its block / lexical scope).

Reusing the iter example:

my $generator = iter [ 1 ... 3 ];

say $generator( ); # >>> 1
say $generator( ); # >>> 2
say $generator( ); # >>> 3
say $generator( ); # >>> False, here, the coroutine is dead.
# Use "$generator = iter [ 1, 2, 3 ];" again if you want...
Coroutine: More complex examples

Following the "coroutines generalize functions" idea (a function may be thought as a coroutine without any yield keywords), we can write map / grep / range functions like coroutines / generators!

# map coroutine
my &transform = coro -> &fn, $xs {
    for @$xs -> $x { yield fn($x) }
}

# grep coroutine
my &filter = coro -> &pred, $xs {
    for @$xs -> $x {
        yield $x if pred($x);
    }
}

# range-like coroutine
my &xrange = coro -> $min, $max {
    for ($min ...^ $max) -> $value {
        yield $value;
    }
}

# Usage:
#
# sub incr ($x) { $x + 1 }      # >>> number.
# sub even ($x) { $x % 2 == 0 } # >>> boolean. use "$x %% 2" if you wish a short version
#
# my $generator = ([ @array ] ==> transform &incr);
# my $filtered  = ([ @array ] ==> filter &even);
# my $get-next  = xrange ($x, $y);
#
# :)
Coroutine: "casting" generator to a lazy list

Thinking in access the values that a generator yields in a nice way? No problem, there's from to solve that. The from function does the opposite from iter above: rather than taking an array and mapping it to a generator, it takes a reference to a generator and returns a lazy array to bind.

Some examples:

my @lazy-array := from $some-generator;

Or, too:

my @lazy-array := from some-constructor ($x, $y, $z);

Build more complex things with it isn't hard entirely, for instance: "pipelines" running on demand, without evaluate the whole thing at all (because map and grep are lazy as well :) ...):

my @lazy-array-1 := (from some-constructor($arg1, $arg2, ...)).map: * + 1;

my @lazy-array-2 := (from (coro { ... })(...)).grep: * %% 2;
Coroutine: Verifying

There's also a function called ensure in this module (the other function, called 'assert', now is deprecated... 'ensure' is the new name for this functionality). Its main purpose is to check a value, so:

  • If given value isn't False, return it.
  • Otherwise, runs given block (other argument).

Let's see a small example below:

$some-value = $some-generator( );

($some-value ==> ensure {
    warn "Sorry, but your coroutine is dead."
}) ==> say;
# prints $some-value or generates a warning if is false

$some-value = ensure ({ $some-generator = some-constructor( ) }, $some-generator( ));
# reassign to the generator if $some-generator returns False or returns a value
Coroutine: Implementing symmetric coroutines

The support to a transfer function is still experimental. Check the 't/transfer.t' test if you wish to know more about. There's also a test about tasks...

Notes

Pull requests are welcome.

Happy Hacking using this module! :)

Tips and Tricks

Normally, it is possible to build a enumerator / generator as this case below ('cause gather / take has a dynamic scope):

# receives many arguments (e.g flattened array) and yields each one
my &iter = coro sub (*@xs) { @xs ==> map &yield }

And some short version (which receives an anonymous list) with:

my &iter = coro { @$^xs.map: &yield }

TODO

  • Insert more examples here (i.e, show the code).

  • Document the module with Perl 6's Pods.

  • Fix the module for the coro function accept lazy-lists / streams (infinite-length lists) as argument.

EOF