Skip to content

Commit

Permalink
📖 docs: adding api documentation for coroutines
Browse files Browse the repository at this point in the history
Signed-off-by: Marco Aurélio da Silva <marcoonroad@gmail.com>
  • Loading branch information
marcoonroad committed Mar 25, 2019
1 parent f5835cb commit ec5f929
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 0 deletions.
6 changes: 6 additions & 0 deletions README.md
Expand Up @@ -79,9 +79,15 @@ Currently, the following abstractions are implemented:
- [sporadic.channels][2], an abstraction for synchronous queues made of many
producers and consumers. This concurrent data type is a bare minimal tool for
pipelines of chained producers and consumers.
- [sporadic.coroutines][3], an abstraction for suspendable subroutines. Such
abstraction also behaves as a task/promise whenever we wait for the final
coroutine result (but keep in mind that coroutines can "loop" forever). It's kinda
like the JavaScript generators, with the sole difference of being asynchronous
instead synchronous calls (and thus, allowing us to mix asynchronous I/O with that).

[1]: https://marcoonroad.github.io/sporadic/streams
[2]: https://marcoonroad.github.io/sporadic/channels
[3]: https://marcoonroad.github.io/sporadic/coroutines

## Remarks

Expand Down
Binary file added docs/assets/images/coroutine-state-machine.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
146 changes: 146 additions & 0 deletions docs/coroutines.md
@@ -0,0 +1,146 @@
# sporadic.coroutines

Coroutines on top of Promises.

Coroutines are an old/classical abstraction in the same sense of Promises. Both
were introduced decades ago, the former is well-known by its native implementation
on the [Lua language][1] (which takes inspiration on both _CLU and Icon languages_),
while the latter, by the yet unknown history on the _E language_ (see the
[history of promises][2]) and the well-known support for JavaScript. The difference
between coroutines and promises is that coroutines are synchronous stateful
procedures and promises are asynchronous effectful computations, but we can
implement one on top of other and vice-versa (this is what we're doing here).

Coroutines are also different from Threads, the former enables _Cooperative
Multi-tasking_ while the latter implies in the _Preemptive_ one. Often said to
generalize subroutines by providing multiple entry-points, coroutines allow us
to express any sort of complex control flow easily, for example, exceptions,
infinite lazy lists, pipes, and so on. This [paper][3] explain 2 kinds of coroutines:
asymmetrical and symmetrical ones. This submodule in the case implements the first version.

## API Usage

Assuming that you've loaded the [UNPKG bundle][4] or installed from NPM and
required that library as `sporadic`, to create a coroutine we will just use
the following pattern:

```javascript
const coroutines = sporadic.coroutines;
const coroutine = await coroutines.create(async function (argument) {
// ...

// supply & demand are arbitrary values
const demand = await this.suspend(supply);

// ...
return result;
});
```

Note here that coroutines only work with asynchronous functions but not asynchronous
arrow functions. It's 'cause arrow functions cannot bind `this`, instead, they
inherit that from lexical/parent scope. That is, this piece of code below would
not work well, and thus, generating buggy coroutines:

```javascript
const coroutine = await coroutines.create(async (argument) => {
// ...
});
```

The `this.suspend(_)` operation is called as that to not collide with the keyword
`yield` of generators, but the idea/behavior resemble the same. By calling
`.suspend()`, you freeze the computation and gives back control for the coroutine's
caller (the passed value will be replied for this caller).
Said that, this `this` object will contain the following structure/typing:

```javascript
const state = this.status()
const demand = await this.suspend(supply)

const demandsStream = this.demands()
const suppliesStream = this.supplies()
```

Where `state` can be either `'CREATED', 'RUNNING', 'SUSPENDED', 'DEAD'` and
both `demandsStream` & `suppliesStream` are sporadic.streams (which subscribe
to the sent and replied values from and to coroutine's caller, respectively).
While `.status()`, `.demands()` and `.supplies()` can be called outside the coroutine
scope, calling `.suspend(_)` outside the associated coroutine scope, that is,
when it's not `'RUNNING'`, will throw an exception during that leaked operation.
This constraint is implemented due buggy issues to not violate our invariants.

---

To run a coroutine, we can use:

```
const output = await sporadic.coroutines.resume(coroutine, input)
```

Where `input` and `output` can be arbitrary values. The `resume` operation
expects a valid coroutine object in either `CREATED` or `SUSPENDED` state, during
the execution it becomes `RUNNING`, and after the promise resolution, it can be
either `SUSPENDED` or `DEAD`. If the coroutine is `CREATED`, the `input` argument
will be passed to the coroutine async function as a parameter. Otherwise, it resolves
a pending `this.suspend(_)` call. After running, if the coroutine is `DEAD`, it's
due a `return` statement or a `throw` one occurring upon execution. In any case,
you can use:

```javascript
const result = await sporadic.coroutines.complete(coroutine)
```

To check the final coroutine result, but keep in mind this is the same promise
outcome of calling `.resume()` and the promise switching to `DEAD` state. In the
`DEAD` state, further calls to `.resume(coroutine, value)` fail, and the associated
supply and demand streams are finally closed. If the coroutine have still pending
computations, it will become `SUSPENDED`, meaning that you can resume that again
later.

To help you to grasp/grok the whole thing, the following state machine diagram
may be useful:

![coroutine-state-machine](assets/images/coroutine-state-machine.jpg)

---

To check the status of any coroutine, we provide:

```javascript
const state = sporadic.coroutines.status(coroutine)
```

It's the same behavior of calling `this.status()` inside the coroutine execution,
the difference is the additional argument to look for some coroutine. The same holds
for `this.supplies()` and `this.demands()`, only if `coroutine` and `this` refer
to the same underlying computation:

```
sporadic.coroutines.demands(coroutine) = this.demands()
sporadic.coroutines.supplies(coroutine) = this.supplies()
```

They are the same stream if the underlying computations are the same, so using the
`sporadic.streams` API on any of them doesn't make any difference at all.

---

To import all public functions within this submodule, this pattern suffices:

```javascript
const {
create, resume, status, demands, supplies
} = sporadic.coroutines;
```

[1]: https://www.lua.org/pil/9.1.html
[2]: https://en.wikipedia.org/wiki/Futures_and_promises#History
[3]: http://www.inf.puc-rio.br/~roberto/docs/MCC15-04.pdf
[4]: https://unpkg.com/sporadic/dist/index.js

<style>
img[alt=coroutine-state-machine] {
width: 100%;
}
</style>
6 changes: 6 additions & 0 deletions docs/index.md
Expand Up @@ -79,9 +79,15 @@ Currently, the following abstractions are implemented:
- [sporadic.channels][2], an abstraction for synchronous queues made of many
producers and consumers. This concurrent data type is a bare minimal tool for
pipelines of chained producers and consumers.
- [sporadic.coroutines][3], an abstraction for suspendable subroutines. Such
abstraction also behaves as a task/promise whenever we wait for the final
coroutine result (but keep in mind that coroutines can "loop" forever). It's kinda
like the JavaScript generators, with the sole difference of being asynchronous
instead synchronous calls (and thus, allowing us to mix asynchronous I/O with that).

[1]: https://marcoonroad.github.io/sporadic/streams
[2]: https://marcoonroad.github.io/sporadic/channels
[3]: https://marcoonroad.github.io/sporadic/coroutines

## Remarks

Expand Down

0 comments on commit ec5f929

Please sign in to comment.