Open1

Moonbit async 調査

mizchimizchi

Effect System が入るらしいので、自分の中の moonbit 熱が再燃している。

これを読みながら実験する。

https://docs.moonbitlang.com/en/latest/language/async-experimental.html

%async.run%async.suspend をディレクティブとして導入している。

これは JSPI の WebAssembly.Suspending によるVM停止と再開に対応していると思われる。

https://github.com/WebAssembly/js-promise-integration/blob/main/proposals/js-promise-integration/Overview.md

JS Backend で試す

moon new mbtplg20250417 して src/main/main.mbt を次の感じにする。

ちなみに lint によると非同期呼び出しの !!()!() になった模様。

fn run_async(f : async () -> Unit) -> Unit = "%async.run"
async fn suspend[T, E : Error](
  f : (
    (T) -> Unit,
    (E) -> Unit
  ) -> Unit
) -> T!E = "%async.suspend"

extern 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 _ = js_set_timeout(fn () { resume_ok(()) }, duration)
  })
}

async fn my_async_function() -> Int {
  42
}
fn main {
  run_async(async fn () {
    try {
      let v = my_async_function!()
      println("the value is \{v}")
      sleep!(500)
      println("the worker finishes")
    } catch {
      err => println("caught: \{err}")
    }
  })
  println("after the first coroutine finishes")
  run_async(async fn () {
    try {
      sleep!(1000)
      println("finish")
    } catch {
      err => println("caught: \{err}")
    }
  })
}

$ moon build --target js でビルドして、 target/js/release/build/main/main.js の生成コードを見てみる

async await にマッピングされるのではなく、継続渡しに変換されてるように見える。

// setTimeout extern 相当
const username$hello$main$$js_set_timeout = (f, duration) => setTimeout(f, duration);

// main 相当
(() => {
  username$hello$main$$my_async_function((_cont_param) => {
    username$hello$main$$_init$42$46$42$async_driver$124$29(new $36$username$47$hello$47$main$46$42$init$46$lambda$47$27$46$State$State_2(_cont_param, username$hello$main$$_init$42$46$42$cont$124$28));
  });
  moonbitlang$core$builtin$$println$3$("after the first coroutine finishes");
  username$hello$main$$sleep(1000, (_cont_param) => {
    username$hello$main$$_init$42$46$42$async_driver$124$45(new $36$username$47$hello$47$main$46$42$init$46$lambda$47$43$46$State$State_1(_cont_param, username$hello$main$$_init$42$46$42$cont$124$44));
  }, (_cont_param) => {
    username$hello$main$$_init$42$46$42$async_driver$124$45(new $36$username$47$hello$47$main$46$42$init$46$lambda$47$43$46$State$_try$47$16(_cont_param, username$hello$main$$_init$42$46$42$cont$124$44));
  });
})();