Moonbit言語の最近の変更点(2024/04~2024/08)
WebAssembly First な言語として自分が注目してる Moonbit 言語、過去に記事を書いてからしばらく経ったんですが、色々と大きな変更がありました。というわけで、新しい言語機能や構文を紹介してきます。
https://www.moonbitlang.com/weekly-updates/ の 4/15 ~ 8/22 を見ています。
日本だと実際に使ってる人がまだいないと思われるので、実用のための変更というより、変更された点を見ることで進化の方向性を掴んでおこう、という趣旨で読むといいでしょう。
配列リテラルがデフォルトでミュータブルに
自分が触ってから最も大きな変更です。(これでほとんどの既存のコードが動かなくなりました)
ミュータブルな@vec.Vec[T]
が Array[T]
にリネームされ、型を省略した際の let x = []
の配列リテラルはイミュータブルな List[T]
から Array[T]
になりました。
fn main {
let x = [1, 2, 3] // Array[Int]
x.push(4)
}
関数型言語から汎用プログラミング言語に寄せた変更と言えそうです。
これに限らず、全体的に関数型言語から汎用プログラミングを意図した変更が多いです。
キーワード引数
引数の宣言で ~x
とチルダをつけることでキーワード引数を作ることが可能になりました。デフォルト引数も取れます。
pub fn f(v : Int, ~x : Int, ~y : Int, ~z : Int = 0) -> Unit {
println(v + x + y + z)
}
fn run_f() -> Unit {
let _ = f(1, x=2, y=3)
let _ = f(1, x=2, y=3, z=4)
}
導入当初、呼び出し側では f(1, ~x=1, ~y=2)
と書いていたんですが、途中から呼び出し側の~
は省略可能になりました。
例外検査
raise, try, catch 等が導入されました。これも言語の根幹に影響する変更です。
// エラー型を宣言する際は type! を使う
type! E1 Int
fn f1() -> Unit!E1 { .. }
fn f2() -> Unit!Error { .. }
fn main {
try f1!() { E1(errno) => println(errno) } // this error handling is complete
try f2!() {
E1(errno) => println(errno)
_ => println("unknown error")
}
}
try は大域脱出の例外処理というより、 raise に対する特殊化されたパターンマッチに見えます。
fn test_try() -> Unit {
fn f() -> Int!String {
raise "err"
}
try {
println("this is try body")
f!()
} except {
err => println(err)
} else {
val => println(val)
}
}
Java の例外検査のように、副作用の宣言やハンドルを必須化する感じっぽいですね。
現状まだ Result<T, E>
もあるので、今後どちらを主流にするつもりなのかが気になります。標準ライブラリの実装を見る限り、基本的に Unit!Error 型に寄せていく方向に見えます。好みが分かれそう。
追記:
Simplified Error Handling Syntax: The syntax for capturing potential errors in function calls has been updated from f(x)!! to f?(x), which returns a Result type.
JSON Literal
ファーストクラスに JSON の型と構文がサポートされました。
// json 型をパターンマッチできる
fn json_process(x : @json.JsonValue) -> Double {
match x {
{
"outer": {
"middle": { "inner": [{ "x": Number(x) }, { "y": Number(y) }] },
},
} => x + y
_ => 0
}
}
fn main {
// json としてキャストされる。
let x : @json.JsonValue = {
"outer": { "middle": { "inner": [{ "x": 42 }, { "y": 24 }] } },
"another_field": "string value",
}
json_process(x) |> println
}
JsonValue 型として潰れてしまうので TypeScript のように型を仮定したJSONとして扱えるわけではないですが、外から渡されるJSONを処理するには便利そうです。
serde 的な型のマッピングとして、シリアライザ/デシリアライザがほしくなりますね。
WebAssembly.Memory の初期化方法
wasm-gc バックエンドを使ってる人しか関係ないんですが、今まで共有メモリが instance.exports['moonbit.memory']
固定だったのが設定でメモリを import したり、export する名前を変更できるようになりました。
main/moon.pkg.json
{
"import": [],
"link": {
"wasm-gc": {
"export-memory-name": "memory",
"exports": ["run"]
}
}
}
let memory: WebAssembly.Memory;
// ...
const obj = await WebAssembly.instantiate(wasm, imports);
const exports = obj.instance.exports as any;
memory = exports.memory as WebAssembly.Memory;
あるいは "import-memory" で import するパスを結び付けられます。
{
"link": {
"wasm-gc": {
"import-memory": {
"module": "env",
"name": "memory"
},
"exports": ["run"]
}
}
}
(TODO: 自分で試す)
今後への期待
これだけではなく他にも全然変わってるのですが、目立つのがこのあたりです。
自分はずっと async/await のような非同期処理が導入させるのを期待しています。これがあれば汎用プログラミング言語として、ホストに依存しないスタンドアロンな言語になれると思うんですが...。
以下は試しに cloudflare workers で使おうとした際の例
若い言語なのでどんどん仕様が変わっていき、すぐに動かなくなります。これは悪い意味ではなく今しかできない試行錯誤だと思うので、今のうちによりよいベースになることを期待しています。
Discussion