← melang

MoonBit

melangの各noteで他言語と比較した、MoonBitの設計をまとめたページ。ここに書かれている内容は、各noteの比較表から自動で集めたものです。

型

WebAssembly向けに設計された新しい言語。

let age : Int = 20
let age : Int = 20
let name = "Alice" // 型推論で String

fn greet(name : String) -> String {
  "Hello, " + name
}

Rust/OCamlに近い構文を持つ、WebAssemblyをファーストクラスのターゲットとする新しい言語。型推論・代数的データ型複数の型を組み合わせて新しい型を作る手法。「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼び、Rustの`enum`やHaskellの`data`が代表例。各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。・パターンマッチを備えつつ、コンパイル速度とバイナリサイズの小ささを重視して設計されている。

型注釈の位置

Rust/Swiftと同系統のpostfix構文で、シグネチャは明示が基本。

let age : Int = 20
let age : Int = 20
let name = "Alice" // 型推論でString

fn greet(name : String) -> String { // 引数・戻り値は明示
  "Hello, " + name
}

`name : Type`というpostfix構文で、Rust/Swiftと同じ系統に属する。ローカル変数は型推論に任せられるが、関数シグネチャは明示が基本という設計も共通しており、新興言語がpostfix系の慣習を踏襲していることが分かる。

型変換

変換をメソッドで書く。Kotlin・Rustに近い設計。

let y = x.to_int64()
let x : Int = 10
let y : Int64 = x.to_int64()
let z : Double = x.to_double()
let s : String = x.to_string()

`to_int64()`, `to_double()`のような変換メソッドを型ごとに持つ。文字列のパースなど失敗し得る変換は、エラーとして表される。melangの記法に近く、エラーを型で表す設計思想も近い。

暗黙の型変換

Rust/Kotlinに近く、数値型間の暗黙変換を持たない。

let x : Int = 10
let x : Int = 10
let y : Double = x               // 型エラー
let y : Double = x.to_double()   // OK

Rust/Kotlinに近い設計思想で、数値型間の暗黙変換を持たない。`to_double()`のような明示的な変換メソッドを呼ぶ必要がある。

Enum / Sum Type

constructorがpayloadを持てるADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。型としてのenumを持つ。

enum Result { Success(User); Failure(String) }
enum Result {
  Success(User)
  Failure(String)
}

fn describe(r : Result) -> String {
  match r {
    Success(user) => "ok: \(user)"
    Failure(message) => "error: \(message)"
  } // 全constructorを網羅しないとコンパイルエラー
}

constructorがpayloadを持てるADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。型としての`enum`を持ち、`match`は全constructorの網羅をコンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。が要求する。RustやOCamlに近い設計で、melangが目指すモデルに近い。

値の不変性

不変と可変を言語上で区別し、可変にするときは`mut`で明示する。

let x = ... / let mut x = ... / mut field
struct User { name : String; mut age : Int }

let a = { name: "alice", age: 20 }
// a.name = "bob"        // エラー: mutではないフィールド
a.age = 21               // OK: mutフィールド

let mut n = 0
n = n + 1                // OK: mutな変数

let arr = [1, 2]
arr[0] = 9               // OK: Arrayは変更できる

不変な束縛・フィールドが既定で、変更するときは`mut`を付けて明示する。`Array`などは変更でき、不変なコレクションは`immut`パッケージとして別に用意される。不変と可変を言語上で区別する点はRustに近い。

名前付き引数

OCamlに近いラベル付き引数を持ち、省略可能なデフォルト値付き引数もある。

fn foo(~width : Int, ~height : Int) -> Int
fn foo(~width : Int, ~height : Int) -> Int {
  width + height
}
foo(~width=10, ~height=20) // ラベル名で明示

let width = 10
foo(~width, ~height=20) // ラベル名と同名の変数なら ~width と省略できる

定義側で引数名の前に`~`を付けるとラベル付き引数になり、呼び出し側も`~label=value`の形で渡す。OCamlのラベル付き引数に近い設計で、変数名がラベルと同じ場合は`~label`と省略できる。

関数の引数

引数は不変。可変な値は型(Ref・Array)で表される。

fn f(n : Int, arr : Array[Int]) -> Unit
fn f(n : Int, arr : Array[Int]) -> Unit {
  // n = 1  // 引数は再代入できない
  arr.push(n) // 渡されたArrayの中身は変更できる
}

fn g(r : Ref[Int]) -> Unit {
  r.val = 1
}

引数は不変で、書き換えたい場合は`Ref[T]`や可変な`Array`など、可変な型の値を渡す。渡し方を選ぶ構文はなく、可変性は型で表される。OCamlと同じ方針で、呼び出し側には印が付かない。

if 文/式

`if`は式。`else`を省略すると`Unit`になり、値を返す場合は`else`が必要になる。

if cond { a } else { b }
let x = if cond { 1 } else { 2 }

if cond {
  process()
}

Rustに近く、`if`は式として扱われる。`else`を省略した`if`は`Unit`を返すため、値を使う場合だけ`else`が必要になる。手続きの`else`も禁止されていない。

I/O の明示

`async`関数は宣言が必要だが、呼び出し側は推論され明示を求めない。

async fn find_user(id : UserId) -> User
async fn find_user(id : UserId) -> User { ... }

fn get_user(id : UserId) -> User {
  find_user(id) // 呼び出しにawait等の印は不要。コンパイラが型から同期/非同期を判定する
}

MoonBitのコルーチン方式はKotlinに近いが、非同期呼び出しに`await`のような印を書く必要がなく、コンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。が型から自動的に判定する。同期的なI/O(標準出力など)は`async`を伴わずに書けるため、シグネチャからI/O全体の有無を判別できる設計ではない。

言語名

"Moon" + "Bit"の合成語。新興言語として一意な名前を最初から設計できた例。

既存語との衝突を避けるため作られた新しい合成語で、ドメイン・パッケージレジストリ・GitHub上の名前空間を後発でも確保しやすい。新興言語は歴史的制約がない分、命名の自由度が高く、衝突回避を最初から設計に組み込める。