Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add document for experimental async programming support #411

Merged
merged 2 commits into from
Jan 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/next-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ jobs:
for dir in next/sources/*; do
if [ -d "$dir" ]; then
echo "Processing $dir"
if [ $(basename "$dir") = async ] && [ ${{ matrix.backend }} != "js" ]; then
continue
fi
if ! (cd "$dir" && moon install && moon check --deny-warn --target ${{ matrix.backend }} && moon test --target ${{ matrix.backend }}); then
echo "Failed in $dir"
failed_directories+=("$dir")
Expand Down Expand Up @@ -77,6 +80,9 @@ jobs:
$failed_directories = @()
Get-ChildItem -Path ".\next\sources" -Directory | ForEach-Object {
Write-Output "Processing $($_.FullName)"
if ($_.Name -eq "async" && "${{ matrix.backend }}" -ne "js") {
return
}
Set-Location $_.FullName
moon install && moon check --deny-warn --target ${{ matrix.backend }} && moon test --target ${{ matrix.backend }}
if (!$?) {
Expand Down
82 changes: 82 additions & 0 deletions next/language/async-experimental.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Experimental async programming support

MoonBit is providing experimental support for async programming.
But the design and API is still highly unstable, and may receive big breaking change in the future.
This page documents the current design, and we highly appreciate any feedback or experiment with current design.

## Async function
Async functions can be declared with the `async` keyword:

```{literalinclude} /sources/async/src/async.mbt
:language: moonbit
:start-after: start async function declaration
:end-before: end async function declaration
```

Async functions must be called with the `!!` operator:

```{literalinclude} /sources/async/src/async.mbt
:language: moonbit
:start-after: start async function call syntax
:end-before: end async function call syntax
```

If the async function may throw error, `!!` will also rethrow the error.

Async functions can only be called in async functions. Currently, async functions cannot be called in the body of `for .. in` loops.

## Async primitives for suspension
MoonBit provides two core primitives for `%async.suspend` and `%async.run`:

```{literalinclude} /sources/async/src/async.mbt
:language: moonbit
:start-after: start async primitive
:end-before: end async primitive
```

There two primitives are not intended for direct use by end users.
However, since MoonBit's standard library for async programming is still under development,
currently users need to bind these two primitives manually to do async programming.

There are two ways of reading these primitives:

- the coroutine reading: `%async.run` spawn a new coroutine,
and `%async.suspend` suspend current coroutine.
The main difference with other languages here is:
instead of yielding all the way to the caller of `%async.run`,
resumption of the coroutine is handled by the callback passed to `%async.suspend`
- the delimited continuation reading: `%async.run` is the `reset` operator in delimited continuation,
and `%async.suspend` is the `shift` operator in delimited continuation

Here's an example of how these two primitives work:

```{literalinclude} /sources/async/src/async.mbt
:language: moonbit
:start-after: start async example
:end-before: end async example
```

In `async_worker`, `suspend` will capture the rest of the current coroutine as two "continuation" functions, and pass them to a callback.
In the callback, calling `resume_ok` will resume execution at the point of `suspend!!(...)`,
all the way until the `run_async` call that start this coroutine.
calling `resume_err` will also resume execution of current coroutine,
but it will make `suspend!!(...)` throw an error instead of returning normally.

Notice that `suspend` type may throw error, even if `suspend` itself never throw an error directly.
This design makes coroutines cancellable at every `suspend` call: just call the corresponding `resume_err` callback.

## Integrating with JS Promise/callback based API
Since MoonBit's standard async library is still under development,
so there is no ready-to-use implementation for event loop and IO operations yet.
So the easiest way to write some async program is to use MoonBit's Javascript backend,
and reuse the event loop and IO operations of Javascript.
Here's an example of integrating MoonBit's async programming support with JS's callback based API:

```{literalinclude} /sources/async/src/async.mbt
:language: moonbit
:start-after: start async timer example
:end-before: end async timer example
```

Integrating with JS Promise is easy too:
just pass `resume_ok` as the `resolve` callback and `resume_err` as the `reject` callback to a JS promise.
1 change: 1 addition & 0 deletions next/language/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ tests
docs
ffi-and-wasm-host
derive
async-experimental
```
Loading
Loading