-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add document for experimental async programming support
- Loading branch information
Showing
7 changed files
with
311 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# 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/language/src/async/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/language/src/async/async.mbt | ||
:language: moonbit | ||
:start-after: start async function call syntax | ||
:end-before: end async function syntax | ||
``` | ||
|
||
If the async function may throw error, `!!` will also rethrow the error. | ||
|
||
## 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 corourtine, | ||
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 operation sof Javascript. | ||
Here's an example of integrating MoonBit's async programming support wtih 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,4 +26,5 @@ tests | |
docs | ||
ffi-and-wasm-host | ||
derive | ||
async-experimental | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"name": "moonbit-community/async-doc", | ||
"version": "0.1.0", | ||
"readme": "README.md", | ||
"repository": "", | ||
"license": "Apache-2.0", | ||
"keywords": [], | ||
"description": "", | ||
"source": "src" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
// start async function declaration | ||
async fn my_async_function() -> Unit { | ||
... | ||
} | ||
|
||
// anonymous/local function | ||
test { | ||
let async_lambda = async fn () { | ||
... | ||
} | ||
async fn local_async_function() { | ||
... | ||
} | ||
} | ||
// end async function declaration | ||
|
||
// start async function call syntax | ||
async fn my_async_function() -> Unit! { | ||
... | ||
} | ||
|
||
fn another_async_function() -> Unit! { | ||
// error will be rethrowed by `!!` | ||
my_async_function!!() | ||
} | ||
// end async function call syntax | ||
|
||
// start async primitive | ||
|
||
// `run_async` spawn a new coroutine and execute an async function in it | ||
fn run_async(f : async () -> Unit) -> Unit = "%async.run" | ||
|
||
// `suspend` will suspend the execution of the current coroutine. | ||
// The suspension will be handled by a callback passed to `suspend` | ||
async fn suspend[T, E : Error]( | ||
// `f` is a callback for handling suspension | ||
f : ( | ||
// the first parameter of `f` is used to resume the execution of the coroutine normally | ||
(T) -> Unit, | ||
// the second parameter of `f` is used to cancel the execution of the current coroutine | ||
// by throwing an error at suspension point | ||
(E) -> Unit | ||
) -> Unit | ||
) -> T!E | ||
// end async primitive | ||
|
||
// start async example | ||
type! MyError derive(Show) | ||
|
||
async fn async_worker(throw_error~ : Bool) -> Unit!MyError { | ||
suspend!!(fn (resume_ok, resume_err) { | ||
if throw_error { | ||
resume_err(MyError) | ||
} else { | ||
resume_ok(()) | ||
println("the end of the coroutine") | ||
} | ||
}) | ||
} | ||
|
||
// the program above should print: | ||
// | ||
// the worker finishes | ||
// the end of the coroutine | ||
// after the first coroutine finishes | ||
// catched MyError | ||
test { | ||
// when supplying an anonymous function | ||
// to a higher order function that expects async parameter, | ||
// the `async` keyword can be omitted | ||
run_async(fn () { | ||
try { | ||
async_worker!!(throw_error=false) | ||
println("the worker finishes") | ||
} catch { | ||
err => println("catched: \{err}") | ||
} | ||
}) | ||
println("after the first coroutine finishes") | ||
run_async(fn () { | ||
try { | ||
async_worker!!(throw_error=true) | ||
println("this message should be printed after the worker finishes") | ||
} catch { | ||
err => println("catched: \{err}") | ||
} | ||
}) | ||
} | ||
// end async example | ||
|
||
// start async timer example | ||
type JSTimer | ||
extern "js" fn js_set_timeout(f : () -> Unit, duration : Int) -> JSTimer = | ||
#| (f, duration) => setTimeout(f, duration) | ||
|
||
async fn sleep(duration : Int) -> Unit! { | ||
suspend!!(fn (resume_ok, _resume_err) { | ||
let _ = setTimeout(resume_ok, duration) | ||
}) | ||
} | ||
|
||
test { | ||
run_async(fn () { | ||
sleep!!(500) | ||
println("timer 1 tick") | ||
sleep!!(1000) | ||
println("timer 1 tick") | ||
sleep!!(1500) | ||
println("timer 1 tick") | ||
}) | ||
run_async(fn () { | ||
sleep!!(600) | ||
println("timer 2 tick") | ||
sleep!!(600) | ||
println("timer 2 tick") | ||
sleep!!(600) | ||
println("timer 2 tick") | ||
}) | ||
} | ||
// end async timer example |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"warn-list": "-6" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
// start async function declaration | ||
async fn my_async_function() -> Unit { | ||
... | ||
} | ||
|
||
// anonymous/local function | ||
fn main { | ||
let async_lambda = async fn () { | ||
... | ||
} | ||
async fn local_async_function() { | ||
... | ||
} | ||
} | ||
// end async function declaration | ||
|
||
// start async function call syntax | ||
async fn my_async_function() -> Unit! { | ||
... | ||
} | ||
|
||
fn another_async_function() -> Unit! { | ||
// error will be rethrowed by `!!` | ||
my_async_function!!() | ||
} | ||
// end async function call syntax | ||
|
||
// start async primitive | ||
|
||
// `run_async` spawn a new coroutine and execute an async function in it | ||
fn run_async(f : async () -> Unit) -> Unit = "%async.run" | ||
|
||
// `suspend` will suspend the execution of the current coroutine. | ||
// The suspension will be handled by a callback passed to `suspend` | ||
async fn suspend[T, E : Error]( | ||
// `f` is a callback for handling suspension | ||
f : ( | ||
// the first parameter of `f` is used to resume the execution of the coroutine normally | ||
(T) -> Unit, | ||
// the second parameter of `f` is used to cancel the execution of the current coroutine | ||
// by throwing an error at suspension point | ||
(E) -> Unit | ||
) -> Unit | ||
) -> T!E | ||
// end async primitive | ||
|
||
// start async example | ||
fn run_async(f : async () -> Unit) -> Unit = "%async.run" | ||
async fn suspend[T, E : Error]( | ||
f : ((T) -> Unit, (E) -> Unit) -> Unit | ||
) -> T!E | ||
|
||
type! MyError derive(Show) | ||
|
||
async fn async_worker(throw_error~ : Bool) -> Unit!MyError { | ||
suspend!!(fn (resume_ok, resume_err) { | ||
if throw_error { | ||
resume_err(MyError) | ||
} else { | ||
resume_ok(()) | ||
println("the end of the coroutine") | ||
} | ||
}) | ||
} | ||
|
||
// the program above should print: | ||
// | ||
// the worker finishes | ||
// the end of the coroutine | ||
// after the first coroutine finishes | ||
// catched MyError | ||
fn main { | ||
run_async(fn () { | ||
try { | ||
async_worker!!(throw_error=false) | ||
println("the worker finishes") | ||
} catch { | ||
err => println("catched: \{err}") | ||
} | ||
}) | ||
println("after the first coroutine finishes") | ||
run_async(fn () { | ||
try { | ||
async_worker!!(throw_error=true) | ||
println("this message should be printed after the worker finishes") | ||
} catch { | ||
err => println("catched: \{err}") | ||
} | ||
}) | ||
} | ||
// end async example |