Skip to content

hongbo/tweak async #850

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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
5 changes: 5 additions & 0 deletions next/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ A new MoonBit docs framework based on Sphinx.

## Develop

You can just run `./autobuild.sh` to start the development server, it will
install the dependencies if they are not installed, and start the server on watch mode.

Below are the instructions for manual setup.

### Install

- For Python Environment, execute command `> Python: Create Environment` for `next` using `requirement.txt` in VSCode, or:
Expand Down
6 changes: 4 additions & 2 deletions next/autobuild.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#!/bin/sh
set -e

sphinx-autobuild . ./_build/html
python3 -m venv .env
source .env/bin/activate
pip install -r requirements.txt
sphinx-autobuild . ./_build/html $@
15 changes: 8 additions & 7 deletions next/language/async-experimental.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Experimental async programming support
# 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.
MoonBit adopts a coroutine based approach to async programming which is similar to [Kotlin](https://kotlinlang.org/docs/coroutines-overview.html).
The compiler support and concrete syntax is stable while the async library is still under development and considered experimental.

<!-- We highly appreciate any feedback or experiment with current design. -->

## Async function
Async functions are declared with the `async` keyword:
Expand All @@ -23,7 +24,7 @@ MoonBit IDE will highlight the async function call with a different style.
:end-before: end async function call syntax
```

Async functions can only be called in async functions.
Async functions can only be called inside async functions.

```{warning}
Currently, async functions
Expand All @@ -46,8 +47,8 @@ currently users need to bind these two primitives manually to do async programmi

There are two ways of reading these primitives:

- The coroutine reading: `%async.run` spawn a new coroutine,
and `%async.suspend` suspend current coroutine.
- The coroutine reading: `%async.run` spawns a new coroutine,
and `%async.suspend` suspends the 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`
Expand Down
4 changes: 3 additions & 1 deletion next/sources/async/moon.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
"license": "Apache-2.0",
"keywords": [],
"description": "",
"source": "src"
"source": "src",
"preferred-target" : "js"

}
142 changes: 90 additions & 52 deletions next/sources/async/src/async.mbt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// start async function declaration
///| start async function declaration
async fn my_async_function() -> Unit {
...
}

// anonymous/local function
///| anonymous/local function
test {
let async_lambda = async fn() { ... }
async fn local_async_function() {
Expand All @@ -15,21 +15,24 @@ test {
// end async function declaration

// start async function call syntax

///|
async fn some_async_function() -> Unit raise {
...
}

///|
async fn another_async_function() -> Unit raise {
some_async_function() // rendered in italic font
}
// end async function call syntax

// start async primitive

/// `run_async` spawn a new coroutine and execute an async function in it
///| `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.
///| `suspend` will suspend the execution of the current coroutine.
/// The suspension will be handled by a callback passed to `suspend`
async fn[T, E : Error] suspend(
// `f` is a callback for handling suspension
Expand All @@ -44,86 +47,121 @@ async fn[T, E : Error] suspend(
// end async primitive

// start async example

///|
suberror MyError derive(Show)

async fn async_worker(throw_error~ : Bool) -> Unit raise MyError {
///|
async fn async_worker(
logger~ : &Logger,
throw_error~ : Bool
) -> Unit raise MyError {
suspend(fn(resume_ok, resume_err) {
if throw_error {
resume_err(MyError)
} else {
resume_ok(())
println("the end of the coroutine")
logger.write_string("the end of the coroutine\n")
}
})
}

// the program below should print:
//
// the worker finishes
// the end of the coroutine
// after the first coroutine finishes
// caught 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")
let logger = StringBuilder::new()
fn local_test() {
run_async(() => try {
async_worker(logger~, throw_error=false)
logger.write_string("the worker finishes\n")
} catch {
err => println("caught: \{err}")
}
})
println("after the first coroutine finishes")
run_async(fn() {
try {
async_worker(throw_error=true)
println("the worker finishes")
err => logger.write_string("caught: \{err}\n")
})
logger.write_string("after the first coroutine finishes\n")
run_async(() => try {
async_worker(logger~, throw_error=true)
logger.write_string("the worker finishes\n")
} catch {
err => println("caught: \{err}")
}
})
err => logger.write_string("caught: \{err}\n")
})
}

local_test()
inspect(
logger,
content=
#|the worker finishes
#|the end of the coroutine
#|after the first coroutine finishes
#|caught: MyError
#|
,
)
}
// end async example

// start async timer example
#external

///|
type JSTimer

///|
extern "js" fn js_set_timeout(f : () -> Unit, duration~ : Int) -> JSTimer =
#| (f, duration) => setTimeout(f, duration)

///|
async fn sleep(duration : Int) -> Unit raise {
suspend(fn(resume_ok, _resume_err) {
js_set_timeout(duration~, fn() { resume_ok(()) }) |> ignore
})
}

///|
test {
run_async(fn() {
try {
sleep(500)
println("timer 1 tick")
sleep(1000)
println("timer 1 tick")
sleep(1500)
println("timer 1 tick")
} catch {
_ => panic()
}
})
run_async(fn() {
try {
sleep(600)
println("timer 2 tick")
sleep(600)
println("timer 2 tick")
sleep(600)
println("timer 2 tick")
} catch {
_ => panic()
}
})
let logger = StringBuilder::new()
fn println(s : String) {
logger.write_string(s)
logger.write_string("\n")
}

fn local_test() {
run_async(fn() {
println("start 1")
try {
sleep(500)
println("timer 1 tick")
sleep(1000)
println("timer 1 tick")
sleep(1500)
println("timer 1 tick")
} catch {
_ => panic()
}
})
run_async(fn() {
println("start 2")
try {
sleep(600)
println("timer 2 tick")
sleep(600)
println("timer 2 tick")
sleep(600)
println("timer 2 tick")
} catch {
_ => panic()
}
})
}

local_test()
inspect(logger, content=
#|start 1
#|start 2
#|


)
}
// end async timer example