mcpField
M
moonbit-agent-guide

moonbit-agent-guide

github.com/moonbitlang開発34277 回インストール6 週間前に更新
リポジトリ

moonbit-agent-guide について

moonbit-agent-guide は、MoonBit プロジェクトを編集するエージェント向けのスキルです。プロジェクト構成、言語上の注意点、ナビゲーションやテスト、引き継ぎ時の確認手順をまとめています。

主な機能

  1. 01MoonBit のモジュールとパッケージ構成ガイド
  2. 02`moon ide doc` によるセマンティックナビゲーション
  3. 03`.mbtx` を使ったエージェント自動化
  4. 04テストと検証のワークフロー
  5. 05言語とコード構成の注意点

活用シーン

  1. 01編集前の MoonBit プロジェクト把握
  2. 02API の検索とシンボルのリファクタリング
  3. 03テストの追加と変更の検証
  4. 04MoonBit による自動化スクリプトの作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/moonbitlang/skills --skill moonbit-agent-guide

インストール後、Claude Code で/moonbit-agent-guideを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

.gitignore
ide.md
LICENSE
README.md
SKILL.mbt.md
references/
advanced-moonbit-build.md
moonbit-language-fundamentals.mbt.md
moonbit-language-fundamentals.md

詳細

MoonBit タスクチェックリスト

  • 編集する前に、包含する moon.mod と関連する moon.pkg ファイルを特定します。
  • moon ide doc で API を確認し、セマンティックなナビゲーションとリファクタリングには outline、peek-def、find-references、hover、rename を使用します。
  • エージェントが作成する自動化には .mbtx のみを使用し、自動化ロジックをシェルスクリプトやその他のスクリプト言語に記述しないでください。
  • 変更は適切なパッケージ内に収めます。ファイルは名前空間を定義しません。トップレベルの項目は ///| で区切ります。
  • バグ修正には回帰テストを追加します。新しい公開 API には、ブラックボックステストと docstring の例を追加します。
  • moon check と、関連する範囲に絞った moon test で検証します。意図したスナップショットの変更にのみ moon test --update を使用します。警告を一覧表示するには moon explain --diagnostic を使用し、選択した警告を有効にするには --warn-list を使用します(例:+unnecessary_annotation)。
  • 引き継ぎ前に moon fmt と moon info を実行します。生成された .mbti の変更を確認します。特に、公開 API を安定させる必要がある場合は注意してください。

MoonBit プロジェクトの構成

MoonBit では、ソースコードファイルと、拡張子 .mbti のインターフェースファイルに .mbt 拡張子を使用します。 MoonBit プロジェクトのトップレベルには、プロジェクトのメタデータを指定する moon.mod ファイルがあります。プロジェクトには複数のパッケージを含めることができ、それぞれに 独自の moon.pkg があります。サブディレクトリには、異なる依存関係のセットを使用できることを示す moon.mod ファイルが含まれる場合もあります。 レガシープロジェクトには、引き続き moon.mod.json が含まれていることがあります。これは古いモジュール メタデータ形式として扱い、新しい moon.mod.json ファイルを作成するのではなく、moon.mod に移行または更新するよう案内してください。

構成例

my_module
├── moon.mod                  # Module metadata; source option can specify the source directory
├── moon.pkg             # Package metadata (each directory is a package like Golang)
├── README.mbt.md             # Markdown with tested code blocks (`test "..." { ... }`)
├── README.md -> README.mbt.md
├── cmd                       # Command line directory
│   └── main
│       ├── main.mbt
│       └── moon.pkg     # executable package with `options("is-main": true)`
├── liba/                     # Library packages
│   └── moon.pkg         # Referenced by other packages as `@username/my_module/liba`
│   └── libb/                 # Library packages
│       └── moon.pkg     # Referenced by other packages as `@username/my_module/liba/libb`
├── user_pkg.mbt              # Root packages, referenced by other packages as `@username/my_module`
├── user_pkg_wbtest.mbt       # White-box tests (only needed for testing internal private members, similar to Golang's package mypackage)
└── user_pkg_test.mbt         # Black-box tests
└── ...                       # More package files, symbols visible to current package (like Golang)
  • モジュール:プロジェクトのルートディレクトリにある moon.mod ファイルによって識別されます。 MoonBit の モジュール は Go モジュールのようなもので、サブディレクトリ内のパッケージの集合体です。通常、リポジトリまたはプロジェクトに相当します。 モジュールの境界は、依存関係の管理とインポートパスに影響します。

  • パッケージ:各ディレクトリにある moon.pkg ファイルによって識別されます。 moon のすべてのサブコマンドは、現在のパッケージではなく、モジュールのディレクトリ(moon.mod が 置かれている場所)で実行されます。 MoonBit の パッケージ は実際のコンパイル単位です(Go のパッケージのようなものです)。 同じパッケージ内のすべてのソースファイルはひとつの単位に連結されるため、 そのパッケージ全体で定義を共有します。 moon.mod ファイル内の name と、パッケージのソースディレクトリまでの相対パスを組み合わせて、 パッケージ名が決まります。ファイル名ではありません。 インポートでは、ファイル名ではなく、モジュールとパッケージのパスを参照します。

  • ファイル: .mbt ファイルは、パッケージ内のソースコードの一部にすぎません。 ファイル名によってモジュール、パッケージ、名前空間が作られることはありません。 同じパッケージ内であれば、宣言を自由に分割、統合、移動できます。 パッケージ内の宣言は、ファイルに関係なく、同じパッケージ内の他の宣言を参照できます。

必ず守るべきコーディング/レイアウトのルール:

  1. 1 つの大きなファイルよりも、まとまりのある小さなファイルを複数使うことを優先してください。

    • 関連する型や関数を、目的に特化したファイルにまとめます(例:http_client.mbt、router.mbt)。
    • ファイルが大きくなりすぎたり、焦点が定まらなくなったりした場合は、新しいファイルを作成し、関連する宣言を移動してください。
  2. 同じパッケージ内であれば、宣言は自由にファイル間で移動してかまいません。

    • 各ブロックは ///| で区切られています。関数/構造体/トレイトをファイル間で移動しても、名前と pub であるかどうかを変えなければ、意味は変わりません。各ブロックの順序も関係ありません。
    • パッケージ内でファイルを分割したり統合したりしても、安全にリファクタリングできます。
  3. ファイル名は整理のためだけのものです。

    • ファイル名がモジュールを定義すると想定してはいけません。また、型のパスにファイル名を使用してはいけません。
    • 型名に厳密に合わせるのではなく、機能や役割を表すファイル名を選んでください。
  4. 新しいコードを追加する場合:

    • その機能に適した既存のファイルへの追加を優先してください。
    • 適切なファイルがない場合は、同じパッケージ内に説明的な名前の新しいファイルを作成してください。
    • 巨大な「impl」「misc」「util」ファイルを作成するのは避けてください。
  5. テスト:

    • テストは、適切なパッケージ内の専用テストファイル(例:*_test.mbt)に配置してください。 パッケージでは(*_test.mbtfiles を除き)、*.mbt.md ファイルも Markdown ファイルに加えてブラックボックステストファイルとして扱われます。 トリプルバッククォートで区切られた mbt check のコードブロックはテストケースとして扱われ、ドキュメントとテストの両方の役割を果たします。 mbt check のコード例を含む README.mbt.md ファイルを用意してもかまいません。また、GitHub との統合をより適切にするために、README.mbt.md を README.md へシンボリックリンクすることもできます。
    • 小さなテストファイルを複数用意するのも問題なく、推奨されます。
  6. インターフェースファイル(pkg.generated.mbti) pkg.generated.mbti ファイルは、各パッケージの公開 API サーフェスをまとめた、コンパイラー生成のファイルです。 実装の詳細を含めずに、エクスポートされるすべての型、関数、トレイトを形式的かつ簡潔に示します。 これらは moon info を使って生成され、コードレビューに役立ちます。公開 API を変更しないコミットでは pkg.generated.mbti ファイルに変更はありません。そのため、作業が完了したら pkg.generated.mbti をバージョン管理に追加することを推奨します。 空白文字のみのクリーンアップを含め、pkg.generated.mbti を直接変更しないでください。moon info で再生成し、公開 API の変更を示す差分としてレビューしてください。

    IDE のナビゲーションおよびシンボル検索コマンドについては、以下の専用の moon ide セクションを参照してください。

よくある落とし穴

  • 変数/関数名に大文字を使わないでください - コンパイルエラーになります
  • 変更可能なレコードフィールドには mut を忘れずに付けてください - デフォルトでは不変です(通常、配列に mut は不要です。ただし、変数に配列全体を再代入する場合は必要です。たとえば、単純な push 操作には不要です)
  • エラー処理を無視しないでください - エラーを明示的に処理するか、呼び出し元で raise を宣言して、チェック対象のエラーを伝播させてください
  • 不要な return は使わないでください - 最後の式が戻り値になります
  • Type:: プレフィックスなしでメソッドを作成しないでください - メソッドには型の明示的なプレフィックスが必要です
  • 配列の境界チェックを忘れないでください - 安全にアクセスするには get() を使ってください
  • 別のパッケージの関数を呼び出すときは、@package プレフィックスを忘れないでください
  • ++ や -- は使わないでください(サポートされていません) - i = i + 1 または i += 1 を使ってください
  • エラー伝播に明示的な try を追加しないでください - raise 関数内では、エラーを発生させる関数を通常どおり呼び出してください。ローカルで処理するには catch を使い、中断が意図されている場合にのみ try! を使ってください
  • レガシー構文:レガシーコードでは function_name!(...) や function_name(...)? が使われている場合があります。これらは非推奨です。伝播には通常の呼び出しを使ってください。
  • main の空のパラメーターリストは書かないでください - fn main { ... } または fn main raise { ... } を使い、fn main() { ... } や fn main() raise ... { ... } は使わないでください
  • レコード形式の列挙型フィールドやエラーコンストラクターのフィールドは書かないでください - ラベル付きコンストラクターフィールドは label~ : Type の形式です。例:InvalidNumber(input~ : String)。InvalidNumber(input: String) ではありません
  • C スタイルのループより範囲指定の for ループを優先してください - for i in 0..<(n-1) {...} や for j in 0..=6 {...} のほうが MoonBit では慣用的です
  • 無限ループに for { ... } を使わないでください - 代わりに for ;; { ... } と書いてください
  • デバッグ目的で derive(Show) を使わないでください - Debug を derive し、テスト/診断出力には debug_inspect() を使ってください(合成値の補間には \{Repr(value)} を使います)。手動の impl Show は、JSON、XML、ドメイン固有のテキストなど、専用の表示形式に限って使ってください
  • @json.inspect() を呼び出さないでください - パッケージプレフィックスなしの prelude の json_inspect(value, ...) を使ってください
  • 非同期 - MoonBit には await キーワードがありません。追加しないでください。非同期関数はデフォルトでエラーを発生させるため、raise を追加しないでください。非同期の本体でエラーを発生させない必要がある場合にのみ noraise を追加してください。 非同期関数およびテストとは、ほかの非同期関数を呼び出すものです。 関数またはテストを非同期として示すには、async プレフィックスを追加するだけです(例:[pub] async fn ...、async test ...)。

moon の基本

スクリプトモード(.mbtx)

エージェントが作成する自動化には、.mbtxのみを使用してください。ループ、 条件分岐、解析、変換、プロセスのオーケストレーションは、シェルスクリプトや ほかのスクリプト言語ではなく、MoonBitで行います。外側のシェルは、 スクリプトの起動、または単一目的のコマンドの直接実行だけに使用してください。

.mbtxファイルは、任意のimport { ... }ブロックの後に、main 関数を含む通常のMoonBitコードを記述したものです。次のように直接実行します。

moon run path/to/script.mbtx [args...]

スクリプトで実際に使用する効果に応じて、mainを選んでください。

  • fn main { ... }は同期的で、エラーを伝播しません。
  • fn main raise { ... }は同期的で、チェック対象のエラーを伝播することがあります。
  • async fn main { ... }は、非同期APIを呼び出すスクリプトに使用します。"moonbitlang/async"をインポートしてください。raiseやawaitは追加しないでください。

3つの形式はすべて、デフォルトのWasmターゲットで動作します。純粋な同期スクリプトにasyncを指定しないでください。MoonBitからunused_asyncが報告されます。

自動化でコマンドを実行する場合は、"moonbitlang/async/shell"をインポートし、@shell.Cmdまたは@shell.Pipelineを使用してください。これらは実行ファイルと引数を分けて保持し、シェルを呼び出さないため、|、$()、*などの文字はそのまま渡されます。&&、シェルのループ、コマンド置換の代わりに、通常のMoonBitの制御フローを使用してください。

スクリプトモードはデフォルトでWasmを使用します。--wasm-policy policy.jsonを指定すると、MoonBitホストAPIに対するデフォルト拒否の制御が有効になります。必要なファイルシステム、環境、ネットワーク、プロセスへのアクセスのみを許可してください。プロセスを自動化する場合は、process.spawn: trueよりも、実行プログラムと引数のプレフィックスを正確に指定した、範囲の狭いprocess.allowルールを優先してください。このポリシーは子プロセスの実行を許可しますが、子プロセス自体をサンドボックス化するものではありません。そのため、信頼できない子プロセスは、ホスト側でも制限する必要があります。

最小限のスクリプト(hello.mbtx):

fn main {
  println("Hello from MoonBit script mode")
}

コマンドライン自動化(sum_args.mbtx):

import {
  "moonbitlang/core/env",
  "moonbitlang/core/string",
}

fn main raise {
  let args = @env.args()
  for arg in args[1:]; total = 0 {
    continue total + @string.parse_int(arg)
  } nobreak {
    println(total)
  }
}

moon run sum_args.mbtx 10 20 12を実行すると、42が出力されます。

パッケージのインポートは、スクリプトヘッダーで直接使用できます(format_json.mbtx):

import {
  "moonbitlang/core/json",
}

fn main raise {
  let source =
    #|{
    #|  "project": "moonbit",
    #|  "enabled": true
    #|}
  let value = @json.parse(source)
  println(value.stringify(indent=2))
}

非同期自動化は、デフォルトのWasmターゲットで動作します。たとえば、 次を実行するにはmoon run async_job.mbtxを実行してください。

import {
  "moonbitlang/async",
}

async fn main {
  @async.sleep(10)
  println("async automation complete")
}

基本的なコマンド

  • moon new my_project - 新しいプロジェクトを作成します
  • moon run cmd/main - メインパッケージを実行します
  • moon run - < hello.mbtx - 標準入力からスクリプトコードを実行します(手早く試すのに便利です)
  • moon run -e "code snippet" - コマンドライン引数からコードを実行します(ワンライナーに適しています)
    moon run -e 'fn main { println("Hello, MoonBit!") }'
    
  • moon build - プロジェクトをビルドします (moon run と moon build はどちらも --target に対応しています。moon build は --diagnostic-limit <N> にも対応しています)
  • moon check - ビルドせずに型をチェックします。高速なので、定期的に実行してください (moon check は --target と --diagnostic-limit <N> にも対応しています)
  • moon info - 型をチェックし、mbti ファイルを生成します。 公開インターフェースに変更があったか確認するために実行してください。 (moon info は --target にも対応しています。)
  • moon check --target all - すべてのバックエンドを対象に型をチェックします 構造化診断の処理には、シェルパイプラインや別のスクリプト言語ではなく、保存した .mbtx スクリプトを使用してください。たとえば、以下を filter_diagnostics.mbtx として保存します。@shell.Cmd::each_line は moon check を直接実行し、JSON 出力をストリーミングして、終了ステータスを返します。

import { "moonbitlang/async", "moonbitlang/async/shell", "moonbitlang/core/json", }

async fn main { let seen : Map[String, Unit] = Map([]) let exit_code = @shell.Cmd( "moon", ["check", "--target", "all", "--output-json"], ).each_line(line => { try @json.parse(line) catch { _ => () } noraise { { "level": "warning", "path": String(path), .. } => if !seen.contains(path) { seen[path] = () println(path) } _ => () } }) if exit_code != 0 { fail("moon check exited with code {exit_code}") } }

このコマンドプレフィックスを許可するポリシーで実行します。
```json
{
  "process": {
    "allow": [
      {
        "program": "moon",
        "args_prefix": ["check", "--target", "all", "--output-json"]
      }
    ]
  }
}
moon run --wasm-policy moon-check-policy.json filter_diagnostics.mbtx
  • moon explain - コンパイラーの診断と言語トピックに関する組み込みドキュメントを表示します。
    • moon explain --diagnostic は警告のニーモニックと ID を一覧表示します。
    • moon explain --diagnostic 31 は警告 31(unused_optional_argument)を説明します。
    • moon explain --diagnostic unused_optional_argument は、同じ警告をニーモニックで説明します。
    • moon explain --attribute は、#deprecated、#alias、#cfg、#coverage.skip、#warnings などのサポート対象属性を一覧表示します。
    • moon explain --attribute deprecated は、#deprecated 属性とそのサポート対象の形式を説明します。
  • moon add package - 依存関係を追加します
  • moon remove package - 依存関係を削除します
  • moon fmt - コードをフォーマットします。定期的に実行してください。ファイルが書き換えられる場合があることに注意してください moon -C dir check を使って、特定のディレクトリでコマンドを実行することもできます。
ホットパスのプロファイリング(moon run --profile)

moon run --profile --target native --release cmd/<main> は、サンプリングプロファイラーの下でネイティブのリリースビルドを実行し、自己時間と包括時間のランキング表に加え、「MoonBit の呼び出し元に帰属するランタイムのリーフコスト」セクションを出力します(割り当て、参照カウント、文字列比較のコストをあなたの関数に対応付けます)。さらに、Instruments で開ける profile.json と .trace も生成します。macOS では Xcode の xcrun xctrace が必要なので、最初にコマンドラインツールだけでなく、Xcode 全体をインストールしてください。単一の解析や計算ではサンプリングに短すぎるため、プロファイリング対象の main を、代表的なフィクスチャに対してホットパスを数百回実行するループにしてください。このループのハーネスは使い捨てであり、コミットしてはいけません。

自己時間でどの関数がサイクルを消費しているかを確認し、包括時間でどの呼び出しサブツリーが支配的かを確認してから、短いサイクルで作業します。プロファイリングし、最上位の項目を修正し、再度プロファイリングします。差分を信頼する前に、必ず基準値を取り直してください。サンプリングによる計測時間はマシンの負荷で変動するため、以前のセッションの数値と比較するのではなく、ブランチと main を交互に続けてビルドし、ベンチマークしてください。

テストコマンド
  • moon test - すべてのテストを実行します (moon test は --target にも対応しています)
  • moon test --update - スナップショットを更新します
  • moon test -v - テスト名を含む詳細な出力を表示します
  • moon test [dirname|filename] - 指定したディレクトリまたはファイルをテストします
  • moon coverage analyze - カバレッジを分析します
  • moon test [dirname|filename] --filter 'glob' - フィルターに一致するテストを実行します
    moon test float/float_test.mbt --filter "Float::*"
    moon test float -F "Float::*" // shortcut syntax
    

README.mbt.md 生成ガイド

  • パッケージディレクトリに README.mbt.md を出力します。 *.mbt.md ファイルとドキュメントコメントの内容では、mbt check が特別に扱われます。 mbt check ブロックはコードとして直接取り込まれ、moon check と moon test によって実行されます。コードスニペットをチェックしたくない場合は、明示的に mbt nocheck を使用することをおすすめします。 パッケージの型を参照するだけの場合は、構文ハイライトのみが適用される mbt nocheck を使用してください。 README.md を想定しているシステムに対応するには、README.mbt.md を README.md にシンボリックリンクします。

テストガイド

動作が変わったときに簡単に更新できるため、スナップショットテストを使用します。

  • スナップショットテスト:inspect(value)/debug_inspect(value)/json_inspect(value) を記述し、moon test --update(または moon test -u)を実行して content= を埋めます。

    • Show を実装する値(プリミティブ、または手動で impl Show した型)には inspect() を使用します。
    • Debug を導出するあらゆる型には debug_inspect() を使用します。独自のデータ型では、これがデフォルトです。
    • 複雑なネスト構造には json_inspect() を使用します(ToJson トレイトを使い、より読みやすい出力を生成します)。
    • 関数の戻り値が大きすぎない場合は、戻り値全体を検査することをおすすめします。これによりテストがシンプルになります。YourType に適宜 Debug および/または ToJson(または impl Show)を導出します。
  • 更新ワークフロー:出力に影響するコードを変更した後、moon test --update を実行してスナップショットを再生成し、テストファイルの差分を確認します(content= パラメーターは自動的に更新されます)。

  • 検証の順序:MoonBit Task Checklist に従ってください。

  • デフォルトではブラックボックステストにします。@package.fn を介して公開 API のみを呼び出してください。プライベートメンバーが重要な場合に限り、ホワイトボックステストを使用します。

  • グループ化:関連するチェックを 1 つの test "..." { ... } ブロックにまとめると、速度と明確さが向上します。

  • パニック:テスト名の先頭に test "panic ..." を付けます。呼び出しが値を返す場合は、警告を抑制するために ignore(...) で囲みます。

  • エラー:成功が期待される場合は、エラーを発生させる関数を直接呼び出します。予期せず呼び出しがエラーを発生させた場合、実際のエラーとともにテストが失敗します。失敗が期待される場合は、try ... catch ... noraise を使用し、catch でエラーを検査して、noraise で明示的に失敗させます。 失敗を期待する場合のデフォルトの形式:try f() catch { err => inspect(err) } noraise { _ => fail("expected to fail") }。

ドキュメントコメントのテスト

公開 API にはドキュメントコメントのテストを含めることをおすすめします。

///|
/// Return the sum of an `Array`.
///
/// # Example
/// ```mbt check
/// test {
///   inspect(sum_array([1, 2, 3, 4, 5, 6]), content="21")
/// }
/// ```
pub fn sum_array(xs : Array[Int]) -> Int {
  xs.fold(init=0, (a, b) => a + b)
}

ドキュメントコメント内の MoonBit コードは、型チェックとテストが自動的に行われます(moon test --update を使用)。ドキュメントコメントでは、mbt check に含めるのは test または async test のみにしてください。

仕様駆動開発

  • 仕様は、スタブコードを宣言としてマークした読み取り専用の spec.mbt ファイル(名前は慣例であり、必須ではありません)に記述できます。

この意図的に不完全な契約は mbt nocheck で示されています。このガイドの一部としてチェックまたは実行する実装はありません。プロジェクトでは、実装が提供されるまで、moon check が未実装の宣言を報告します。

///|
declare pub type Yaml

///|
declare pub fn Yaml::to_string(y : Yaml) -> String raise

///|
declare pub impl Eq for Yaml

///|
declare pub fn parse_yaml(s : String) -> Yaml raise
  • 仕様の関数をテストするために、spec_easy_test.mbt、spec_difficult_test.mbt などを追加します。すべて型チェックされます(moon check)。

  • パッケージ構成のおかげで、AI またはユーザーは別々のファイルで declare 関数を実装できます。

  • moon test を実行して、すべてが正しいことを確認します。

  • declare は関数、メソッド、型でサポートされています。

  • pub type Yaml の行は、意図的に不透明なプレースホルダーです。実装者がその表現を選びます。

  • 仕様ファイルには、宣言だけでなく通常のコードも含められることに注意してください。

moon ide [doc|peek-def|outline|find-references|hover|rename|analyze] によるコードナビゲーションとリファクタリング

プロジェクト内のシンボルやナビゲーションには、次のコマンドを使用します。

  • moon ide doc <query> を使って、MoonBit で利用可能な API、関数、型、メソッドを見つけます。利用可能な API を調べるときは、常にほかの方法より moon ide doc を優先してください。grep_search や正規表現ベースの検索ツールよりも強力で正確です。
  • moon ide outline . を使ってパッケージをスキャンします。
  • moon ide find-references <symbol> を使って使用箇所を検索します。
  • moon ide peek-def を使ってインラインの定義コンテキストを確認し、トップレベルシンボルを探します。
  • moon ide hover sym --loc filename:line:col を使って、特定の位置の型情報を取得します。
  • moon ide rename <symbol> <new_name> [--loc filename:line:col] を使って、プロジェクト全体でシンボルの名前を変更します。シンボル名があいまいな場合は、--loc を優先して指定してください。
  • moon ide analyze [path] を使って、リファクタリングの安全性を検討する際に、パッケージまたはモジュールの公開 API の使用状況を調べます。 これらのツールはトークンを節約でき、grep よりも正確です(grep はコメントも含め、定義箇所と呼び出し箇所の両方に結果を表示します)。
API の探索に使う moon ide doc

moon ide doc は、シンボル検索に特化したクエリ構文を使用します。

  • 空のクエリ:moon ide doc ''

    • モジュール内:依存関係と moonbitlang/core を含む、現在のモジュールで利用可能なすべてのパッケージを表示します。
    • パッケージ内:現在のパッケージにあるすべてのシンボルを表示します。
    • パッケージ外:利用可能なすべてのパッケージを表示します。
  • 関数/値の検索:moon ide doc "[@pkg.]value_or_function_name"

  • 型の検索:moon ide doc "[@pkg.]Type_name"(組み込み型にはパッケージのプレフィックスは不要です)

  • メソッド/フィールドの検索:moon ide doc "[@pkg.]Type_name::method_or_field_name"

  • パッケージの探索:moon ide doc "@pkg"

    • パッケージ pkg と、そのエクスポートされたシンボルの一覧を表示します。
    • 例:moon ide doc "@json" — @json パッケージ全体を探索します。
    • 例:moon ide doc "@encoding/utf8" — ネストされたパッケージを探索します。
  • 複数のクエリ:moon ide doc "query1" "query2" ...

    • 1 回の実行で複数のクエリを実行し、結果をまとめます。
    • 例:moon ide doc "String" "Array" "@json" — 複数の型とパッケージを一度に探索します。
  • ワイルドカード検索:部分一致には * ワイルドカードを使います。たとえば、moon ide doc "String::*rev*" を実行すると、名前に「rev」を含むすべての String メソッドを検索できます。

moon ide doc の例
# search for String methods in standard library:
$ moon ide doc "String"

type String

  pub fn String::add(String, String) -> String
  # ... more methods omitted ...

$ moon ide doc "@buffer" # list all symbols in package buffer:
moonbitlang/core/buffer

fn from_array(ArrayView[Byte]) -> Buffer
# ... omitted ...

$ moon ide doc "@buffer.new" # list the specific function in a package:
package "moonbitlang/core/buffer"

pub fn new(size_hint? : Int) -> Buffer
  Creates ... omitted ...


$ moon ide doc "String::*rev*"  # globbing
package "moonbitlang/core/string"

pub fn String::rev(String) -> String
  Returns ... omitted ...
  # ... more

pub fn String::rev_find(String, StringView) -> Int?
  Returns ... omitted ...

ベストプラクティス:このセクションはコマンドリファレンスとして扱い、検証方法は MoonBit Task Checklist で定義されています。

moon ide rename sym new_name [--loc filename:line:col] の例

ユーザーから「関数 compute_sum の名前を calculate_sum に変更できますか?」と依頼された場合:

$ moon ide rename compute_sum calculate_sum --loc math_utils.mbt:2

*** Begin Patch
*** Update File: cmd/main/main.mbt
@@
 ///|
 fn main {
-  println(@math_utils.compute_sum(1, 2))
+  println(@math_utils.calculate_sum(1, 2))
 }
*** Update File: math_utils.mbt
@@
 ///|
-pub fn compute_sum(a: Int, b: Int) -> Int {
+pub fn calculate_sum(a: Int, b: Int) -> Int {
   a + b
 }
*** Update File: math_utils_test.mbt
@@
 ///|
 test {
-  inspect(@math_utils.compute_sum(1, 2))
+  inspect(@math_utils.calculate_sum(1, 2))
 }
*** End Patch
moon ide hover sym --loc filename:line:col の例

ユーザーから「hover.mbt の 14 行目にある filter のシグネチャとドキュメント文字列を教えてください」と尋ねられた場合:

$ moon ide hover  filter --loc hover.mbt:14
test {
  let a: Array[Int] = [1]
  inspect(a.filter((x) => {x > 1}))
            ^^^^^^
            ```moonbit
            fn[T] Array::filter(self : Array[T], f : (T) -> Bool raise?) -> Array[T] raise?
            ```
            ---
            
             Creates a new array containing all elements from the input array that satisfy
             ... omitted ...
}
moon ide peek-def sym [--loc filename:line:col] の例

ユーザーから「Parser::read_u32_leb128 が正しく実装されているか確認できますか?」と尋ねられた場合、 定義のコンテキストを取得するには moon ide peek-def Parser::read_u32_leb128 を実行できます (プロジェクト全体を意味に基づいて検索するため、grep より適しています)。

L45:|///|
L46:|fn Parser::read_u32_leb128(self : Parser) -> UInt raise ParseError {
L47:|  ...
...:| }

Parser 構造体の定義を確認するには、次のコマンドを実行します。

$ moon ide peek-def Parser --loc src/parse.mbt:46:4
Definition found at file src/parse.mbt
  | ///|
2 | priv struct Parser {
  |             ^^^^^^
  |   bytes : Bytes
  |   mut pos : Int
  | }
  |

--loc 引数では、行番号を正確に指定する必要があります。位置引数 Parser が位置の特定に役立つため、列番号はおおよその値でかまいません。

「sym」がトップレベルシンボルの場合、位置は省略できます。

$ moon ide peek-def String::rev
Found 1 symbols matching 'String::rev':

`pub fn String::rev` in package moonbitlang/core/builtin at /Users/usrname/.moon/lib/core/builtin/string_methods.mbt:1039-1044
1039 | ///|
     | /// Returns a new string with the characters in reverse order. It respects
     | /// Unicode characters and surrogate pairs but not grapheme clusters.
     | pub fn String::rev(self : String) -> String {
     |   self[:].rev()
     | }
moon ide outline [dir|file] and moon ide find-references <sym> for Package Symbols

moon ide outlineを使うと、パッケージやファイルをスキャンしてトップレベルのシンボルを一覧表示し、grepを使わずに使用箇所を特定できます。

  • moon ide outline dirは、現在のパッケージディレクトリのアウトラインを表示します(ファイルごとのヘッダー付き)
  • moon ide outline parser.mbtは、単一ファイルのアウトラインを表示します これは、パッケージの概要をすばやく把握したいときや、peek-defの前に適切なファイルを見つけたいときに便利です。
  • moon ide find-references TranslationUnitは、現在のモジュール内でシンボルへのすべての参照を検索します
$ moon ide outline .
spec.mbt:
 L003 | pub(all) enum CStandard {
        ...
 L013 | pub(all) struct Position {
        ...
$ moon ide find-references TranslationUnit

パッケージ管理

依存関係の追加
moon add moonbitlang/x        # Add latest version
moon add moonbitlang/[email protected]  # Add specific version
依存関係の更新
moon update                   # Update package index
サードパーティーソースの閲覧(moon fetch)

moon fetch <author>/<module>[@<version>]は、パッケージのソースをオフラインで閲覧できるよう、.repos/<author>/<module>/<version>/にダウンロードします(例、内部実装、生成された.mbti)。これはパッケージをmoon.modに追加しません。その場合はmoon addを使用してください。.repos/を.gitignoreに追加してください。

moon fetch moonbitlang/[email protected]   # browse source/examples without taking a dependency
一般的なモジュール設定(moon.mod)
name = "username/hello"
version = "0.1.0"
readme = "README.mbt.md"
repository = ""
license = "Apache-2.0"
keywords = []
description = "..."

import {
  "moonbitlang/[email protected]",
}

options(
  // source: "src", // Optional; default is "."
  "preferred-target": "native",
)

依存関係のバージョンを手作業で編集する代わりに、moon add moonbitlang/[email protected]とmoon remove moonbitlang/xを使ってimportブロックを管理してください。

一般的なパッケージ設定(moon.pkg)

moon.pkgを簡潔にする

import {
  "username/hello/liba",
  "moonbitlang/x/encoding" @libb,
}
import {
  "username/hello/test_helpers",
} for "test"
import {
  "username/hello/internal_test_helpers",
} for "wbtest"
options(
  "is-main": true,
)

パッケージ全体が特定のバックエンドのみをサポートする場合は、トップレベルでsupported_targets = "native"または別のターゲットセット式を使用してください。

supported_targets = "native"
options(
  "is-main": true,
)

パッケージはディレクトリごとに定義され、moon.pkgファイルがないパッケージは認識されません。

パッケージのインポート(moon.pkgで使用)
  • インポート形式:"module_name/package_path"
  • 使用方法:@alias.function()でインポートした関数を呼び出します
  • デフォルトのエイリアス:パスの最後の部分です(例:username/hello/libaの場合はliba)
  • パッケージ参照:テスト対象のパッケージを参照するには、テストファイルで@packagenameを使用します

パッケージのエイリアス規則:

  • "username/hello/liba"をインポート → @liba.function()を使用します(デフォルトのエイリアスはパスの最後の要素です)
  • カスタムエイリアスでインポート:import { "moonbitlang/x/encoding" @enc} → @enc.function()を使用します (パスの最後の要素がエイリアス名と同じ場合は、これを行う必要はありません。)
  • _test.mbtまたは_wbtest.mbtファイルでは、テスト対象のパッケージが自動的にインポートされます

例:

///|
/// In main.mbt after importing "username/hello/liba" in `moon.pkg`
fn main {
  println(@liba.hello()) // Calls hello() from liba package
}
標準ライブラリの使用(moonbitlang/core)

moonbitlang/coreモジュールは、moon.modの依存関係に追加しなくても常に利用できます。通常のcoreパッケージでは、@utf8、@json、@strconvなどのパッケージエイリアスを使うため、moon.pkgで明示的にインポートする必要があります。コンパイラーがcoreパッケージの不足または暗黙的な使用を報告した場合は、"moonbitlang/core/encoding/utf8"のようなインポートを追加してください。

パッケージの作成

.の下に新しいパッケージfibを追加するには:

  1. ディレクトリを作成します:./fib/

  2. ./fib/moon.pkgを追加します

  3. コードを含む.mbtファイルを追加します

  4. 依存するパッケージでインポートします:

      import {
        "username/hello/fib",
      }
    

conditional compilation、link configuration、warning control、pre-build commandsなどのより高度なトピックについては、references/advanced-moonbit-build.mdを参照してください。

非同期 I/O

非同期プログラミングでは、コンパイラーのサポートと moonbitlang/async ランタイムを使用します。async fn main は、ランタイムをインポートすれば、デフォルトの Wasm ターゲットとネイティブターゲットの両方で動作します。個々のホスト I/O API はターゲット固有の場合があるため、moon ide doc "@async" を使用し、対象のターゲットで検証してください。

ユーザー向けのサブパッケージには、@async(タスク、タイマー、キャンセル)、 @async/aqueue、@async/fs、@async/shell(シェルを介さないプロセスのオーケストレーション)、 @async/stdio、@async/websocket があります。 それぞれを moon.pkg または .mbtx のインポートブロックで個別にインポートする必要があります。

  1. moon add moonbitlang/async で依存関係を追加します。
  2. 実行可能ファイルの moon.pkg で is-main を設定し、必要なものをインポートします:
    import {
      "moonbitlang/async",
      "moonbitlang/async/stdio",
    }
    options(
      "is-main": true,
    )
    
  3. async fn main を定義します(fn main ではありません)。構造化並行性には with_task_group を使って並行タスクを生成します:
    ///|
    async fn main {
      @async.with_task_group(group => {
        group.spawn_bg(() => {
          @async.sleep(50)
          @stdio.stdout.write("A\n")
        })
        group.spawn_bg(() => {
          @async.sleep(20)
          @stdio.stdout.write("B\n")
        })
      })
    }
    
  • 非同期関数には、デフォルトでエフェクト raise があります。async fn main { ... } または async fn f(...) { ... } と記述し、async fn main raise { ... } とは記述しないでください。
  • 非同期本体でエラーを発生させないようにする必要がある場合に限り、async fn f(...) noraise { ... } を使用します。noraise の非同期関数は、エラーをローカルで処理しない限り、失敗する可能性のある API を呼び出せません。

with_task_group の構造化並行性に関する契約:

  • with_task_group が返るとき、グループ内で生成されたすべてのタスクは必ず終了しています。孤立したタスクやリソースリークは発生しません。
  • 生成されたタスクのいずれかが失敗した場合(かつ allow_failure=true を指定せずに生成された場合)、グループ全体が失敗します。グループ内の他のすべてのタスクがキャンセルされ、エラーが with_task_group の外へ伝播します。
  • キャンセルされたタスクは失敗とは見なされません。キャンセルエラーは発生しますが、他のタスクのキャンセルは引き起こしません。

spawn_bg / spawn のクロージャー構文:

  • ✅ () => { ... } — 慣用的な構文です。非同期かどうかはコンテキストから推論されます。
  • ✅ async fn() { ... } — 明示的な注釈で、アロー形式と同等です。
  • ⚠️ fn() { ... } — Warning [0027] deprecated_syntax が発生します:「この fn は非同期ですが、async が注釈されていません」。使用しないでください。
  • ❌ async () => ...、fn() async { ... }、fn(args) async { ... } — いずれも構文解析エラーになります。async は fn の前にのみ置けます。アローラムダの前や、パラメーターリストの後には置けません。
非同期テスト

非同期関数を呼び出すテストには async test を使用します。テストを含むパッケージでは、テストモード用に moonbitlang/async をインポートする必要があります。テストで使用する非同期サブパッケージも、同じ for "test" ブロックでインポートしてください。

import {
  "moonbitlang/async",
  "moonbitlang/async/stdio",
} for "test"
///|
async test "sleep completes" {
  @async.sleep(1)
  inspect("done", content="done")
}
  • await キーワードはありません(エラーを発生させる関数と同様です)。async test 内では、非同期関数を通常どおり呼び出してください。
  • async test にもデフォルトで非同期の raise エフェクトがあります。raise を追加しないでください。
  • 非同期テストはデフォルトで並列実行されます。各テストでリソースを分離しない限り、ポート、ファイル、環境変数、グローバルな可変状態の共有は避けてください。
  • 対象のターゲットで moon test を実行してください。テスト対象の API でネイティブ実行が必要な場合に限り、--target native を追加します。テスト名や非同期スケジューリングの動作を確認するには、moon test -v を使用してください。
  • README.mbt.md とドキュメント文字列では、mbt check ブロックに async test ブロックを含められます。該当するテストモード用に、パッケージが moonbitlang/async をインポートしていることを確認してください。

MoonBit 言語ツアー

基本事項

  • 式指向:if、match、ループは値を返し、最後の式が戻り値になります。
  • デフォルトで参照:配列/マップ/構造体は参照を介して変更されます。プリミティブ型を変更可能にするにはRef[T]を使用します。
  • ブロック:トップレベルの項目は///|で区切ります。コードはブロック単位で生成します。 ブロック(波括弧で囲まれた部分)内に空行が必要な場合は、空行の後にコメント行を追加します(コメントのテキストはあってもなくてもかまいません)。
  • 可視性:fnはデフォルトで非公開です。pubを指定すると、許可された範囲で読み取り/構築が可能になります。pub(all)を指定すると、外部からの構築も可能になります。
  • 命名規則:値/関数にはlower_snake、型/列挙型にはUpperCamelを使用します。列挙型のバリアント名はUpperCamelで始めます。
  • パッケージ:コードファイル内でimportは使用しません。@alias.fnで呼び出します。インポートはmoon.pkgで設定します。
  • プレースホルダー:不完全な実装を示すMoonBitコードでは、...は有効なプレースホルダーです。
  • グローバル値:デフォルトでは不変で、通常は型注釈が必要です。
  • ガベージコレクション:MoonBitにはGCがあり、ライフタイム注釈も所有権システムもありません。 Rustとは異なり、F#と同様に、変数を再代入する場合にのみlet mutが必要で、構造体のフィールドや配列/マップの要素を変更する場合には必要ありません。

MoonBitのエラー処理(検査対象エラー)

MoonBitでは、未検査の例外ではなく、検査対象のエラーをスローする関数を使用します。すべてのエラーはErrorのサブタイプであり、suberrorを使用して独自のエラー型を宣言できます。

検査対象エラーは関数シグネチャで追跡され、呼び出し箇所ごとにマークされるわけではありません。エラーを発生させる可能性がある関数は、raiseまたはraise SomeErrorを宣言します。呼び出し元がエラーを上位に渡すだけの場合、呼び出し元も互換性のあるraiseを宣言し、エラーを発生させる関数を通常どおり呼び出します。

  • raise関数内での通常の呼び出し:自動的に伝播します。
  • fn main raise { ... }は、エラーを伝播させる同期コマンドラインの確認用コードや小さな例で有効です。非同期のエントリーポイントにはasync fn main { ... }を使用します。非同期関数はデフォルトでエラーを発生させることができます。
  • suberrorのコンストラクターでは、ラベル付きペイロードにlabel~ : Typeを使用します。呼び出し時およびパターンマッチ時にはlabel=valueを指定します。
  • expr catch { ... }またはtry { ... } catch { ... }:明示的に処理します。
  • try! expr:エラーが発生した場合に中断します。

伝播のためにSwift形式のtryを追加しないでください。新しいコードでは、従来のfunction_name!(...)またはfunction_name(...)?構文を使用しないでください。

///|
/// Declare error types with 'suberror'
suberror ValueError {
  ValueError(String)
}

///|
/// Tuple struct to hold position info
struct Position(Int, Int) derive(ToJson, Debug, Eq)

///|
/// ParseError is subtype of Error
pub(all) suberror ParseError {
  InvalidChar(pos~ : Position, Char) // pos is labeled
  InvalidEof(pos~ : Position)
  InvalidNumber(pos~ : Position, String)
  InvalidIdentEscape(pos~ : Position)
} derive(Eq, ToJson, Debug)

///|
/// Functions declare what they can throw
fn parse_int(s : String, position~ : Position) -> Int raise ParseError {
  // 'raise' throws an error
  if s is "" {
    raise ParseError::InvalidEof(pos=position)
  }
  @string.parse_int(s) catch {
    _ => raise ParseError::InvalidNumber(pos=position, s)
  }
}

///|
/// Declare a specific error type when callers should handle it precisely
fn div(x : Int, y : Int) -> Int raise ValueError {
  if y is 0 {
    raise ValueError::ValueError("Division by zero")
  }
  x / y
}

///|
test "expected success calls directly" {
  inspect(div(6, 3), content="2")
}

///|
test "expected failure handles the raised error" {
  try div(1, 0) catch {
    ValueError::ValueError(message) =>
      inspect(message, content="Division by zero")
  } noraise {
    _ => fail("expected to fail")
  }
}

// Three ways to handle errors:

///|
/// Propagate automatically
fn use_parse(s : String, position~ : Position) -> Int raise ParseError {
  // This plain call is the correct propagation syntax.
  // `try! parse_int(...)` would abort instead of propagating.
  let x = parse_int(s, position~) // label punning, equivalent to position=position
  // Error auto-propagates by default.
  // Unlike Swift, you do not need to mark `try` for functions that can raise
  // errors; the compiler infers it automatically. This keeps error handling
  // explicit but concise.
  x * 2
}

///|
/// Use try! to abort if it raises, no raise in the signature 
fn use_parse2(position~ : Position) -> Int {
  let x = try! parse_int("123", position~) // label punning
  x * 2
}

///|
/// Handle with try-catch
fn handle_parse(s : String, position~ : Position) -> Int {
  parse_int(s, position~) catch {
    ParseError::InvalidEof(pos=_) => {
      println("Parse failed: InvalidEof")
      -1 // Default value
    }
    _ => 2
  }
}

///|
test "error propagation and handling" {
  let position = Position(0, 0)
  inspect(use_parse("21", position~), content="42")
  inspect(use_parse2(position~), content="246")
  inspect(handle_parse("", position~), content="-1")
  inspect(handle_parse("invalid", position~), content="2")
}

重要:エラーを発生させる可能性がある関数を呼び出す際、エラーを伝播させるだけであれば、マーカーを付ける必要はありません。コンパイラーが推論します。 非同期関数は、明示しなくても自動的にエラーを発生させることができます。伝播のために非同期関数にraiseを追加しないでください。未処理のエラーを拒否する必要がある場合にのみ、noraiseを追加します。

整数、Char、オーバーロードされるリテラル

MoonBitはByte、Int16、Int、UInt16、UInt、Int64、UInt64などをサポートしています。 型が判明している場合、リテラルはオーバーロードできます。

///|
test "integer and char literal overloading disambiguation via type in the current context" {
  let (int, uint, uint16, int64, byte) : (Int, UInt, UInt16, Int64, Byte) = (
    1, 1, 1, 1, 1,
  )
  // The literal `1` is overloaded based on the expected type in the current context.
  // compile time error if the literal cannot be represented in the target type, 
  // e.g. let a7 : Byte = 256 // ❌ won't compile, 256 exceeds Byte max value 255
  assert_eq(int, uint16.to_int())
  assert_eq(uint, 1U)
  assert_eq(int64, 1L)
  assert_eq(byte, b'\x01')
  let (a1, a2, a3) : (Int, Char, UInt16) = ('b', 'b', 'b')
  // char literal overloading, `a1` will be the unicode value of 'b', 
  // compile time error when the literal cannot be represented in the target type 
  // e.g, let a6 : UInt16 = '𐍈' // ❌ won't compile, '𐍈' is U+10348, which exceeds UInt16 max value 0xffff  
  let a4 : Byte = b'b' // Byte literal
  assert_eq(a1, 98)
  assert_eq(a2, 'b')
  assert_eq(a3.to_int(), a1)
  assert_eq(a4, b'b')
}

バイト(不変)

///|
test "bytes literals" {
  let b0 : Bytes = b"abcd"
  let b1 : Bytes = [0xff, 0x00, 0x01] // Array literal overloading
  assert_eq(b1.length(), 3)
  guard b0 is [b'a', ..] && b0[1] is b'b' else {
    // Bytes can be pattern matched as BytesView and indexed
    fail("unexpected bytes content")
  }
}

配列(サイズ変更可能)

///|
test "array literals overloading: disambiguation via type in the current context" {
  let (a0, a1, a2, a3) : (
    Array[Int],
    FixedArray[Int],
    ReadOnlyArray[Int],
    ArrayView[Int],
  ) = ([1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3])
  // The literal `[1, 2, 3]` is overloaded based on the expected type in the current context.
  // Defaults to Array[_]
  assert_eq(a0.length(), 3)
  assert_eq(a1.length(), 3)
  assert_eq(a2.length(), 3)
  assert_eq(a3.length(), 3)
}

文字列(不変のUTF-16)

s[i]はコード単位(UInt16)を返し、s.get_char(i)はChar?を返します。 MoonBitでは文字リテラルのオーバーロードがサポートされているため、次のようなコードスニペットを記述できます。

///|
test "string indexing and utf8 encode/decode" {
  let s = "hello world"
  let b0 : UInt16 = s[0]
  guard b0 is ('\n' | 'h' | 'b' | 'a'..='z') && s is [.. "hello", .. rest] else {
    fail("unexpected string content")
  }
  guard rest is " world" else { fail("unexpected string suffix") }

  // In check mode (expression with explicit type), ('\n' : UInt16) is valid.

  // Using get_char for Option handling
  let b1 : Char? = s.get_char(0)
  assert_true(b1 is Some('a'..='z'))

  // ⚠️ Important: Variables won't work with direct indexing
  let eq_char : Char = '='
  assert_false(s.get_char(0) == Some(eq_char))
  // s[0] == eq_char // ❌ Won't compile - eq_char is not a literal, lhs is UInt while rhs is Char
  // Use: s[0] == '=' or s.get_char(0) == Some(eq_char)
  // Requires `"moonbitlang/core/encoding/utf8"` in `moon.pkg`.
  let bytes = @utf8.encode("中文")
  assert_true(bytes is [0xe4, 0xb8, 0xad, 0xe6, 0x96, 0x87])
  let s2 : String = @utf8.decode(bytes) // decode utf8 bytes back to String
  assert_true(s2 is "中文")
  for c in "中文" {
    let _ : Char = c // unicode safe iteration
    println("char: \{c}") // iterate over chars
  }
}
文字列補間とStringBuilder

MoonBitでは文字列補間に\{}を使用します。カスタム型では、トレイトShowを実装する必要があります。

///|
test "string interpolation basics" {
  let name : String = "Moon"
  let config = { "cache": 123 }
  let version = 1.0
  println("Hello \{name} v\{version}") // "Hello Moon v1"
  // ✅ Quoted map keys are allowed inside interpolation expressions.
  println("  - Checking if 'cache' section exists: \{config["cache"]}")
  let sb = StringBuilder()
  sb <+ "[\{[ for x in [1, 2, 3] => "\{x}" ].join(",")}]"
  inspect(sb, content="[1,2,3]")
  let x = 42
  let streamed = StringBuilder()
  streamed <+ "hello \{x}"
  inspect(streamed, content="hello 42")
}

\{}内の式は1行で記述する必要があります。 入れ子の補間と文字列リテラルはサポートされていますが、\{}内で改行することはできません。

<+ and <? マクロによるストリーミング補間

文字列補間は、<+ を使って Logger/StringBuilder のようなライターに直接ストリーミングするか、<? を使ってオプションのライターに条件付きでストリーミングできます。

writer <+ "hello \{x}"
writer <+ {"key1": value, "key2": value2}
lhs <? "hello \{x}"
lhs <? {"key1": value, "key2": value2}

これはライターへの呼び出しに展開されます。

writer.write_string("hello ")
writer.write(x)
writer.write_object_begin()
writer.write_object_field("key1", value)
writer.write_object_field("key2", value2)
writer.write_object_end()
if lhs is Some(l) { l <+ "hello \{x}" }

文字列リテラルのセグメントには write_string を使い、補間式には write を使います。 <? では、None の場合は書き込みを行いません。Some(writer) の場合は、ラップされたライターに同じ <+ の展開を適用します。 <+ と <? の右辺には、テンプレート文字列/複数行テンプレート文字列、またはマップオブジェクトリテラルを指定する必要があります。任意の式は指定できません。 この展開はマクロ形式です。テンプレート文字列に対する write_string と write、およびマップオブジェクトリテラルに対する write_object_begin、write_object_field、write_object_end を、writer 型がどのように実装しているかによって動作が決まります。HTMLBuilder や JSONBuilder などの型では、同じ構文で補間とストリーミングをサポートしながら、異なる意味付けを行えます。 MoonBit では外部型にローカルメソッドを定義できるため、パッケージで既存のライター型にこれらのローカルなライターメソッドを追加し、この構文に対応させることができます。

複数行文字列
///|
test "multi-line string literals" {
  let multi_line_string : String =
    #|Hello "world"
    #|World
    #|
  let multi_line_string_with_interp : String =
    $|Line 1 ""
    $|Line 2 \{1+2}
    $|
  // no escape in `#|`,
  // only escape '\{..}` in `$|`
  assert_eq(multi_line_string, "Hello \"world\"\nWorld\n")
  assert_eq(multi_line_string_with_interp, "Line 1 \"\"\nLine 2 3\n")
}

Map(可変、挿入順を保持)

///|
test "map literals and common operations" {
  // Map literal syntax
  let map : Map[String, Int] = { "a": 1, "b": 2, "c": 3 }
  let empty : Map[String, Int] = Map([]) // Empty map
  // From array of pairs
  let from_pairs : Map[String, Int] = Map::from_array([("x", 1), ("y", 2)])
  assert_true(empty.is_empty())
  inspect(from_pairs["x"], content="1")

  // Set/update value
  map["new-key"] = 3
  map["a"] = 10 // Updates existing key

  // Get value - returns Option[T]
  guard map is { "new-key": 3, "missing"? : None, .. } else {
    fail("unexpected map contents")
  }

  // Direct access (panics if key missing)
  let value : Int = map["a"] // value = 10
  inspect(value, content="10")

  // Iteration preserves insertion order
  for k, v in map {
    println("\{k}: \{v}") // Prints: a: 10, b: 2, c: 3, new-key: 3
  }

  // Other common operations
  map.remove("b")
  guard map is { "a": 10, "c": 3, "new-key": 3, .. } && map.length() == 3 else {
    // "b" is gone, only 3 elements left
    fail("unexpected map contents after removal")
  }
}

ビュー型

重要な概念:ビュー型(StringView、BytesView、ArrayView[T])は、基になるデータをコピーせずに範囲への読み取り専用アクセスを提供します。ビューを受け取るパラメーターには、暗黙の変換によって対応する String、Bytes、または配列も渡せます。逆方向の変換には、ビューを明示的に実体化する必要があります。

  • String → StringView:s[:]、s[start:end]、s[start:]、または s[:end] を使用
  • Bytes → BytesView:b[:]、b[start:end] などを使用
  • Array[T]、FixedArray[T]、ReadOnlyArray[T] → ArrayView[T]:a[:]、a[start:end] などを使用

文字列の境界:オフセットは UTF-16 コード単位で数えます。無効な境界をどのように扱うかに応じて、操作を選択してください。

  • s[a:b](clamped_view)は範囲外のオフセットをクランプし、サロゲートペアを分割しないように境界を内側へ調整します。エラーやパニックは発生せず、範囲の開始が終了より後の場合は空のビューになります。
  • s.exact_view(start=a, end=b) は正確で有効な境界を要求し、それ以外の場合はパニックになります。
  • s.get_view(start=a, end=b) は境界が無効な場合に None を返します。
///|
test "string view boundaries and ownership" {
  let s = "ab😀cd"
  inspect(s[0:3], content="ab")
  inspect(s[3:6], content="cd")
  inspect(s[0:100], content="ab😀cd")
  inspect(s[4:2], content="")
  assert_true(s.get_view(end=3) is None)
  let owned : String = s.exact_view(start=2, end=4).to_owned()
  inspect(owned, content="😀")
}

ビューを使用する場面:

  • 残余パターン([first, .. rest])によるパターンマッチング
  • アロケーションのオーバーヘッドなしでスライスを関数に渡す
  • 大きなシーケンスの不要なコピーを回避する

.to_owned() を使うと、StringView、BytesView、ArrayView[T] を String、Bytes、Array[T] として実体化できます。以前のビュー変換メソッド .to_string()、.to_bytes()、.to_array() は非推奨です。(moon ide doc StringView)

ユーザー定義型(enum、struct)

///|
enum Tree[T] {
  Leaf(T) // Unlike Rust, no comma here
  Node(left~ : Tree[T], T, right~ : Tree[T]) // enum can use labels
} derive(Debug, ToJson) // derive traits for Tree

///|
pub fn Tree::sum(tree : Tree[Int]) -> Int {
  match tree {
    Leaf(x) => x
    // we don't need to write Tree::Leaf, when `tree` has a known type
    Node(left~, x, right~) => left.sum() + x + right.sum() // method invoked in dot notation
  }
}

///|
struct Point {
  x : Int
  y : Int
} derive(Debug, ToJson) // derive traits for Point

///|
pub fn Point::Point(x~ : Int, y~ : Int) -> Point {
  { x, y, }
}

///|
test "user defined types: enum and struct" {
  let tree = Node(left=Leaf(1), 2, right=Leaf(3))
  inspect(tree.sum(), content="6")
  json_inspect(Point(x=10, y=20), content={ "x": 10, "y": 20 })
  debug_inspect(
    Point(x=10, y=20),
    content=(
      #|{ x: 10, y: 20 }
    ),
  )
}

関数型 for ループ

///|
pub(all) enum SearchIndex {
  Found(Int)
  InsertionPoint(Int)
} derive(Debug, Eq)

///|
pub fn binary_search(arr : ArrayView[Int], value : Int) -> SearchIndex {
  let len = arr.length()
  // functional for loop:
  // initial state ; [predicate] ; [post-update] {
  // loop body with `continue` to update state
  //} nobreak { // exit block
  // }
  // predicate and post-update are optional
  for i = 0, j = len; i < j; {
    // post-update is omitted, we use `continue` to update state
    let h = i + (j - i) / 2
    if arr[h] < value {
      continue h + 1, j // functional update of loop state
    } else {
      continue i, h // functional update of loop state
    }
  } nobreak { // exit of for loop
    if i < len && arr[i] == value {
      Found(i)
    } else {
      InsertionPoint(i)
    }
  }
}

///|
test "functional for loop control flow" {
  let arr : Array[Int] = [1, 3, 5, 7, 9]
  debug_inspect(binary_search(arr, 5), content="Found(2)") // Array to ArrayView implicit conversion when passing as arguments
  debug_inspect(binary_search(arr, 6), content="InsertionPoint(3)")
  // for iteration is supported too
  for i, v in arr {
    println("\{i}: \{v}") // `i` is index, `v` is value
  }
}

命令型ループよりも読みやすく、動作を推論しやすいため、可能な限り、関数型 for ループを使用することを強く推奨します。

ラベル付きパラメーターとオプションパラメーター

良い例:ラベル付きパラメーターとオプションパラメーターを使用する

///|
fn g(
  positional : Int,
  required~ : Int,
  optional? : Int, // no default => Option
  optional_with_default? : Int = 42, // default => plain Int
) -> String {
  // These are the inferred types inside the function body.
  let _ : Int = positional
  let _ : Int = required
  let _ : Int? = optional
  let _ : Int = optional_with_default
  // `Repr` renders Option without relying on its deprecated `Show` implementation.
  "\{positional},\{required},\{Repr(optional)},\{optional_with_default}"
}

///|
test {
  inspect(g(1, required=2), content="1,2,None,42")
  inspect(g(1, required=2, optional=3), content="1,2,Some(3),42")
  inspect(g(1, required=4, optional_with_default=100), content="1,4,None,100")
}

誤用:arg : Type? はオプションパラメーターではありません。 呼び出し側は引き続き(None/Some(...) として)値を渡す必要があります。

///|
fn with_config(a : Int?, b : Int?, c : Int) -> String {
  "\{Repr(a)},\{Repr(b)},\{c}"
}

///|
test {
  inspect(with_config(None, None, 1), content="None,None,1")
  inspect(with_config(Some(5), Some(5), 1), content="Some(5),Some(5),1")
}

アンチパターン:arg? : Type?(デフォルト値なしで Option が二重になります)。 デフォルト値付きのオプションパラメーターにする場合は、b? : Int = 1 と記述し、b? : Int? = Some(1) とは記述しないでください。

///|
fn f_misuse(a? : Int?, b? : Int = 1) -> Unit {
  let _ : Int?? = a // rarely intended
  let _ : Int = b
}
// How to fix: declare `(a? : Int, b? : Int = 1)` directly.

///|
fn f_correct(a? : Int, b? : Int = 1) -> Unit {
  let _ : Int? = a
  let _ : Int = b
}

///|
test {
  f_misuse(b=3)
  f_misuse(a=Some(5), b=2) // works but confusing
  f_correct(b=2)
  f_correct(a=5)
}

悪い例:arg : APIOptions(代わりにラベル付きオプションパラメーターを使用してください)

///|
/// Do not use struct to group options.
struct APIOptions {
  width : Int?
  height : Int?
}

///|
fn not_idiomatic(opts : APIOptions, arg : Int) -> Int {
  opts.width.unwrap_or(arg) * opts.height.unwrap_or(arg)
}

///|
test {
  // Hard to use in call site
  inspect(not_idiomatic({ width: Some(5), height: None, }, 10), content="50")
  inspect(not_idiomatic({ width: None, height: None, }, 10), content="100")
}

MoonBit パッケージ構成ガイドライン

パッケージは、コンストラクター、フィールド、パターンマッチング、メソッドをユーザーが利用することを想定した、公開具象型を所有する必要があります。所有元はファサードパッケージ自体でも、ファサードが意図的に再エクスポートする非内部の公開パッケージでもかまいません。

公開型の所有元は、実装上の配置より重要です。ユーザーがある型を @foo.X として認識するなら、特に X::method を呼び出したり、レコードを構築したり、列挙型コンストラクターをパターンマッチングしたり、生成された .mbti ドキュメントを利用したりする場合、X はパッケージ foo または foo が再エクスポートする公開パッケージで定義する必要があります。

MoonBit では、型が非内部の公開パッケージから再エクスポートされている場合、メソッド検索時に所有元のパッケージが暗黙的に読み込まれることがあります。たとえば、@foo が公開パッケージ @bar の type X を再エクスポートしている場合、外部ユーザーは値を @foo.X として記述しながら、@bar.X が所有するメソッドを呼び出せます。

実装の補助には internal/* パッケージを使用してください。

  • スキャナー
  • サブ構文用パーサー
  • エスケープ/エンコード用ヘルパー
  • 検証用ヘルパー
  • 低レベルアルゴリズム
  • 非公開のヘルパー結果型

公開具象 API 型を internal/* に配置し、再エクスポートによってファサードで完全な API を復元しようとしないでください。外部ユーザーには内部パッケージのメソッド所有元が同じように暗黙的に読み込まれないため、x がファサードから再エクスポートされた型として型付けされていても、x.method() が失敗することがあります。また、コンストラクター、生成インターフェース、プライバシーの境界を把握しにくくなります。

using を正しく使用する

pub using はファサードの使いやすさのために使用し、型の所有には使用しないでください。

適切な使用例:

// root package

///|
pub using @parser {parse, parse_fragment}

///|
pub using @dom {type Node, type NodeKind, to_markdown}

///|
pub using @serializer {type HtmlContext}

@parser、@dom、@serializer がすでにそれらの API を所有する公開パッケージである場合に適しています。

内部パッケージからの値の適切な再エクスポート:

///|
pub using @impl {decode_entities}

エクスポートする関数シグネチャが内部型を公開せず、その値を公開 API として意図的に公開したい場合は、これで問題ありません。

リスクのある使用例:

///|
pub using @internal_impl {type X}

公開具象型にはこれを使用しないでください。X が公開型なら、ファサードパッケージまたは非内部の公開パッケージで定義してください。X が本当に内部型なら、公開具象型として公開しないでください。

次のことが必要な場合は、pub using の代わりに明示的なラッパーを使用してください。

  • 内部ヘルパーの結果を公開型に変換する
  • 公開デフォルトを適用する
  • 内部ヘルパー型を隠す
  • 公開 API の所有元を明確に保つ
  • .mbti をレビューしやすくする

実践的なルール

公開関数が X を返し、ユーザーが X を調べたり、構築したり、パターンマッチングしたり、そのメソッドを呼び出したりする必要がある場合、X はユーザーが指定するファサードパッケージ、またはファサードが再エクスポートする非内部の公開パッケージに属する必要があります。

ヘルパーパッケージが別のパッケージ用にデータを計算するだけなら、internal/* の下に配置してもかまいません。ただし、その型は内部にとどめるか、公開ファサードを通じて公開しない単純なヘルパー結果型にしてください。

適切なパッケージ境界の例:

foo/
  types.mbt        // public Foo, FooMode, FooResult
  api.mbt          // public functions and Foo::methods
  private_impl.mbt // private implementation files in same package

internal/foo/
  scanner.mbt
  escaping.mbt
  validation.mbt

コメント