marcoonroad / Coro-Simple

Simple coroutines for Perl 6, inspired by the Lua coroutines. :railway_track: :butterfly: :train: :vertical_traffic_light:
Artistic License 2.0
2 stars 3 forks source link

Todo-list (to improve the overall library). #4

Open marcoonroad opened 8 years ago

marcoonroad commented 8 years ago

Being more clear, so, here, an yield function will be typed as Str ⊳ Int, giving to the whole coroutine a Coroutine[ supply<Str>, demand<Int> ] typing. Int x ← yield “example”

With this two-way-passing stuff, we can derive pipes just like/with: consumer : Coroutine[ demand<α>, supply<β> ] producer : Coroutine[ demand<γ>, supply<α> ]

Nonetheless, these static/generic/parametric typing rules/guarantees can be discarded in favor of some simple implementation (and as result, unityped and unsafe), thus requiring some additional effects at runtime, e.g, exceptions.

marcoonroad commented 8 years ago

And it means that the current API -- poorly-done under some viewpoint -- must be broken... or maybe I'll create a new module in this library for that.

marcoonroad commented 6 years ago

BUMP:

The implementation is quite trivial, once we use two channels and implement coroutines as synchronized tasks, for instance, the resume could be defined as:

method resume($value) {
    $!input-channel.send($value);
    return $!output-channel.receive;
}

And the coroutine function itself would have the following shape:

my $coroutine-function = &yield -> {
    ...
}

Internally, the &yield callback to be passed would be:

sub ($value) {
    if $*THREAD.id == $!coroutine-thread.id {
        $!output-channel.send($value);
        return $!input-channel.receive;
    }
    else {
        die "Yield called outside the owner coroutine!";
    }
}
marcoonroad commented 5 years ago

BUMP v2:

An ownership system on thread-level may impose a burden. Therefore, it demands an ownership transfer feature as well. On the other side, it is possible as well to implement locks/mutex around the generator/step procedure.

https://github.com/marcoonroad/Coro-Simple/blob/081a5e91b58a84095ff523d89babf765498f5a3f/lib/Coro/Simple.pm#L14-L25

But the mutex only ensures that only one thread enters the generator/step procedure scope, issues and gotchas regarding non-determinism can arise here. It's a trade of tradeoffs to think carefully yet.

marcoonroad commented 5 years ago

To improve performance, coroutines could run on another thread. It would give a non-deterministic scheduling, only if the queue of pending coroutines is randomly shuffled / permuted. By running on another thread, other threads can maintain a bidirectional channel for such coroutine scheduler thread. Streams aren't a good solution 'cause we have only one worker here, it makes no sense to maintain an append-only log structure where the clients and the worker are "coupled" in some sense. To avoiding blocking the coroutine scheduler thread, we can make things more asynchronous, for instance:

resume : ('a, 'b) coroutine -> 'a -> 'b promise
yield : 'b -> 'a promise
produced : ('a, 'b) coroutine -> 'b stream
consumed : ('a, 'b) coroutine -> 'a stream

Where yield and resume are non-blocking operations, and produced and consumed are stream logs for the execution context of the coroutine. Whenever the coroutine finishes (by returning or throwing), the stream logs are closed and further resume attempts will fail.

To maintain our desired deterministic coroutine nature, the client just must call await directly on resume and yield results, for example:

...
my $result = await resume($coroutine, $input);
...
marcoonroad commented 5 years ago

:point_right: In this sense, coroutines could be regarded as bidirectional synchronous channels + worker promise, where we feed such worker with inputs and block waiting responses from him.