mcpField
M
moonbit-practice

moonbit-practice

github.com/mizchi開発41660 回インストール1 週間前に更新
リポジトリ

moonbit-practice について

AIによる MoonBit コード生成のための実践ガイドです。構文、プロジェクト設定、テスト、コード検索を扱い、MoonBit v0.10.12で確認された注意点をまとめています。

主な機能

  1. 01MoonBit の構文と型の指針
  2. 02moon ide によるコード検索
  3. 03プロジェクトとワークスペース設定
  4. 04テストとスナップショットの実践
  5. 05構文上のよくある落とし穴

活用シーン

  1. 01現行構文に沿った MoonBit コード生成
  2. 02MoonBit プロジェクトでの API 定義検索
  3. 03モジュール、パッケージ、ワークスペースの設定
  4. 04テスト作成とスナップショット更新

インストール

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

$npx skills add https://github.com/mizchi/skills --skill moonbit-practice

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

同梱ファイル

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

README.md
assets/
ci-nix.yaml
ci.yaml
moon.mod
moon.pkg
publish-to-npm.yaml
reference/
configuration.md
ffi-native.md
ffi.md
ide.md
language.md
mbtx.md
nix.md
performance.md
refactor.md
skills-marketplace.md
stdlib.md
testing.md

詳細

MoonBit 実践ガイド

MoonBit コードを生成する際の AI 向けベストプラクティスです。 内容が分からない場合は、moonbit-docs スキルを使って公式ドキュメントを検索してください。

MoonBit v0.10.12 で検証済み(ドキュメントのスナップショットは 2026-09)。MoonBit はまだ 1.0 より前の段階にあり、マイナーリリースでも構文が変更されることがあります。ここに記載された構文が拒否された場合は、 まず moon fmt を実行してください(非推奨の構文のほとんどは自動移行されます)。その後、 moon explain <error-code> を確認してください。

ガイドライン

コードのナビゲーション:Read/Grep より moon ide を優先する

MoonBit プロジェクトでは、Read ツールや grep よりも moon ide コマンドを優先してください。

# ❌ Avoid: Reading files directly
Read src/parser.mbt

# ✅ Recommended: Find definitions from symbols
moon ide peek-def Parser::parse
moon ide doc 'Parser::*parse*'      # glob search over symbols

# ❌ Avoid: Searching with grep
grep -r "fn parse" .

# ✅ Recommended: Semantic search
moon ide find-references parse
moon ide outline src/parser.mbt

理由:

  • moon ide はセマンティック検索を提供します(定義と呼び出し箇所を区別します)
  • grep ではコメントや文字列もヒットします
  • moon ide doc を使うと API をすばやく確認できます

moon がインストールされていない場合(サンドボックス、レビュー専用のチェックアウト)、 このガイドが前提としている検証手順(moon fmt、moon check、moon test -u、 moon ide doc)は利用できません。その場合:

  • 後で moon test -u が値を補完できるよう、すべてのスナップショットの content="" はそのままにします。
  • reference/stdlib.md はパッケージの概要であり、シグネチャを網羅した索引ではありません。必要な シンボルが記載されておらず、moon ide doc も実行できない場合は、うろ覚えの構文ではなく、このガイドに 記載された構文を優先し、未検証であることを明記してください。
  • 検証できていない部分を、要約で明確に伝えてください。

ツールが生成するはずの値を手書きしないでください。

その他のルール
  • 実装前に moon ide doc '<Type>' を使って API を調べてください。moon doc <SYMBOL> は非推奨です。警告を表示し、moon ide doc を案内します
  • 設定形式には DSL の moon.mod / moon.pkg を使用してください。 JSON 形式の moon.mod.json / moon.pkg.json は非推奨で、削除が予定されています。moon fmt で JSON から DSL に移行できます。サンプル:assets/moon.mod、assets/moon.pkg。いずれかを編集する前に reference/configuration.md を確認してください
  • 実行可能パッケージには pkgtype(kind: "executable") を使います。 options("is-main": true) は使いません。pkgtype(kind: "foreign_library") は options(link: true) に代わるものです。通常のライブラリでは宣言は不要です
  • 1 つのリポジトリに複数のモジュールがある場合は、単独のモジュールではなくワークスペース(moon.work)を使ってください。 moon work init <dirs> でマニフェストを作成できます。メンバーはビルドコンテキストを共有し、互いをローカルで解決します。moon.mod 内のパス依存関係は非推奨で、moon.work の使用が推奨されています。reference/configuration.md の「Workspace」を参照してください
  • 公開用フィルターは .moonignore に記述します(存在しない場合は .gitignore にフォールバックします)。moon.mod の非推奨フィールド include / exclude は使いません
  • 以下の例では pub を省略しています。 下流パッケージまたは JS/Wasm ホストから見える必要があるものには pub が必要です(pub fn、pub struct、pub suberror、pub impl、pub extend)。パッケージ内でのみ使う項目には不要です
  • テストファイル:*_test.mbt はブラックボックステスト(パッケージの公開 API のみを参照できます)、*_wbtest.mbt はホワイトボックステスト(プライベート項目も参照できます)です。単独の test "..." { } ブロックは、これらのいずれかに記述するか、ホワイトボックステストの場合はソースファイル内に記述します。ブラックボックステストでは、追加のインポートなしでパッケージエイリアス(@mypackage.f())を使って同じパッケージにアクセスできます。import { ... } for "test" のエントリーが必要なのは、使用する他のパッケージのみです
  • 実行可能ファイルのエントリーポイント:fn main { ... } — パラメーターリストも括弧もありません。非同期プログラムでは async fn main { ... } を使います。引数は @env.args() で読み取ります。パッケージには pkgtype(kind: "executable") が必要です
  • 可視性:pub は名前を公開します。pub(all) はさらに、構造体のフィールド、または列挙型や suberror のコンストラクターも公開します。これらは別のパッケージでパターンマッチングするために必要です。通常の pub struct / pub suberror は外部から読み取り専用です。構築やパターンマッチングができるのは、そのパッケージ内のみです
  • このスキルのリファレンスファイル:reference/language.md(型、トレイト、パターンマッチング)、reference/testing.md(テストの構成、スナップショット、プロパティテスト、ベンチマーク)、reference/stdlib.md(コア API)、reference/configuration.md(moon.mod / moon.pkg / ワークスペース)、reference/ide.md(moon ide)、reference/ffi.md と reference/ffi-native.md(FFI)、reference/performance.md、reference/refactor.md、reference/nix.md、reference/mbtx.md、reference/skills-marketplace.md。競合がある場合は、SKILL.md を優先してください。
  • 単一ファイルの .mbtx スクリプト(moon run script.mbtx、moonx script.mbtx、moon run - による stdin、moon run -c '...' によるインラインソース、import { } のプリアンブルブロック)については、reference/mbtx.md を確認してください
  • skills.mooncakes.io 向けの実行可能ファイルを定義または公開する前に、reference/skills-marketplace.md を確認してください。パッケージ内の SKILL.md、Wasm ターゲット、moonx、ホスト機能、ポリシー、リリース検証について説明しています

よくある落とし穴

  • 変数/関数名に大文字を使わないでください — コンパイルエラーになります
  • mutは再代入にのみ使い、フィールドの変更には使いません — 配列への追加にmutは必要ありません
  • returnは不要です — 最後の式が戻り値になります
  • メソッドにはType::プレフィックスが必要です
  • ++と--はサポートされていません — i = i + 1またはi += 1を使ってください
  • エラーの伝播にtryは不要です — 自動的に行われます(Swiftとは異なります)
  • awaitキーワードはありません — async fnで宣言するだけです
  • Cスタイルより範囲ベースのforを優先してください — for i in 0..<n {...}
  • 関数型forループの終了値にはelseではなくnobreakを使ってください(elseは非推奨です)
  • レガシー構文:function_name!(...)とfunction_name(...)?は非推奨です
  • for { ... }は非推奨です — 無限ループにはfor ;; { ... }またはwhile true { ... }を使ってください
  • 別パッケージのトレイトメソッドで.を使うにはpub extendが必要です — implだけでは、別パッケージからvalue.f()を使えるようにはなりません。明示的にアタッチしてください(pub extend Type with Trait::{ f })。pubを付けない場合、アタッチはパッケージ内に限定されます。また、extendがなければ呼び出し側はTrait::f(value)と書く必要があります
  • trait/implのメソッドにはfnキーワードが必要です(v0.10.0) — moon fmtが古いコードを移行します。下記の「トレイトまたはimplの移行」を参照してください
  • トレイトメソッドはextendでアタッチしてください(v0.10.4) — 暗黙のimplからメソッドへのアタッチは非推奨です。下記の「トレイトまたはimplの移行」を参照してください
  • try?は廃止されました(v0.10.0) — ResultにはOk(expr) catch { e => Err(e) }を使い、エラー時にパニックさせるにはtry!を使ってください
  • 構造体のfn new(..)コンストラクターは削除されました(v0.10.0) — ユーザー定義コンストラクターfn Type::Type(..)を使ってください。v0.10.4以降は構造体に限らず、あらゆる型で定義できます
  • immut/arrayは削除されました(v0.10.9) — v0.9で非推奨になりました。moon.pkgに"moonbitlang/core/immut/vector"をインポートし(デフォルトのエイリアスは@immut/vector、つまりmoonbitlang/core/の後のパスです)、@immut/vector.Vectorを使ってください
  • 不変コンテナーのfrom_arrayは非推奨です(v0.10.4) — 型名を使ったコンストラクターを使ってください。例:@immut/vector.Vector([1, 2, 3])
  • View演算子では負のインデックスが使えなくなりました(v0.10.4) — xs[-1:]は「末尾から」を意味しなくなりました
  • moonbitlang/sysは非推奨です(v0.10.9) — moonbitlang/core/envを使ってください:let argv = @env.args()(要素0はプログラムを表すため、CLI引数はargv[1:]です)
  • @debug.to_repr(x)は非推奨です(v0.10.9) — @debug.Repr(x)を使ってください
  • elseのないguardは、必ず成功すると証明できる必要があります(v0.10.9) — そうでない場合はE0087になります。パニックさせる場合はguard!と書いてください
  • orパターンのwith分岐には括弧が必要です(v0.10.9) — Some(x) | (None with x = 0)
  • コンテナー型/カスタム型ではinspectの代わりにdebug_inspectを使ってください(v0.9.2) — コンテナー型(Array/Map/Set/Option/Result/タプル)のShowは非推奨です。「スナップショットテスト」を参照してください
  • デフォルトのビルドターゲットはwasmです(v0.10.9) — --target js/--target nativeを明示的に指定するか、moon.modでpreferred_targetを設定してください

AIがよく間違える構文

型パラメーターの位置
///| NG: fn identity[T] is old syntax
fn identity[T](val: T) -> T { val }

///| OK: Type parameter comes right after fn
fn[T] identity(val: T) -> T { val }
raise構文
///|
/// NG: -> T!Error was removed
fn parse(s: String) -> Int!Error { ... }

///|
/// OK: Use raise keyword
fn parse(s: String) -> Int raise Error { ... }

Int raiseはInt raise Errorの省略形です。 async fnはデフォルトで暗黙的にエラーを発生させます。エラーを発生させないようにするにはnoraiseを使ってください。

マクロ呼び出し
///|
/// NG: ! suffix was removed
assert_true!(true)

///|
/// OK
assert_true(true)
トレイトまたはimplの移行

同じコードに対して常に2つの変更が同時に適用されるため、1回の作業で両方を行ってください。

  1. v0.10.0 — トレイトメソッドとimplの両方にfnを追加します。
  2. v0.10.4 — 呼び出し側がドット構文(value.f())で呼び出すトレイトメソッドごとに、extend宣言を追加します。これがないと、ドット呼び出しは非推奨の暗黙的なアタッチに依存します。

moon fmtが実行するのはステップ1のみです。ステップ2は手動で行います。

///| NG: method without fn (deprecated, warns then removed)
trait I {
  f(Self) -> Unit
}
impl I for Int with f(_) {}

///| OK: fn keyword on both the trait method and the impl
trait I {
  fn f(Self) -> Unit
}
impl I for Int with fn f(_) {}

多相トレイトメソッドでは、メソッド固有の型パラメーターは**fnの後**に置き、impl固有の型パラメーターはimplの後に置きます。

trait Logger {
  fn[X : Show] write_object(Self, X) -> Unit
}
impl Logger for StringBuilder with fn write_object(self, x) {
  self.write_string(x.to_string())
}

impl[A] Poly for Array[A] with fn[X] f(self, x : X) { ... }
//   ^^^ impl type params                  ^^^ method type params

moon fmtは古いトレイト/impl構文を自動的に書き換えます。

extend でトレイトメソッドを付与する(v0.10.4)

以前は、impl Trait for Type を記述すると、すべてのトレイトメソッドが暗黙的に Type のドット呼び出し可能なメソッドとして付与されていました。これは非推奨です。上流のトレイトにデフォルトメソッドが追加されると、既存の value.f() が曖昧になる可能性があります。代わりに、付与するメソッドを明示的に宣言してください。

///| NG (deprecated): relying on implicit attachment
impl Show for Point with fn output(self, logger) { ... }
let s = p.output(logger)   // implicit attachment, warns under warning 79

///| OK: attach the methods you want callable with dot syntax
impl Show for Point with fn output(self, logger) { ... }
pub extend Point with Show::{ output, to_string }
  • 基本形:extend Type with Trait::{ method1, method2 }。pub を付けると、付与されたメソッドが公開されます。付けない場合はパッケージ内に限定されます。
  • 付与されたメソッドがトレイトのデフォルト実装を使う場合、Self は Type に特殊化されます。
  • トレイトオブジェクトにも付与できます:extend &Derived with Super::{ method }。
  • extend がない場合も、トレイト経由で呼び出せます:Trait::method(value, ...)。
  • 警告 implicit_impl_as_method(79)は非推奨のパターンを検出します。デフォルトでは無効なので、ライブラリを移行する際は有効にしてください(moon check --warn-list +79)。
構造体、コンストラクター、メソッド

削除された特殊形式 fn new(..) は、通常のユーザー定義コンストラクター fn Type::Type(..) に置き換えられます。これは v0.10.4 以降、構造体に限らずあらゆる型で利用できます。メソッドではレシーバーを self : Self として受け取ります。

///| Struct with derives
struct Point {
  x : Int
  y : Int
} derive(Eq, Debug)

///| User-defined constructor (replaces removed `fn new`)
fn Point::Point(x : Int, y : Int) -> Point {
  { x, y }  // field-punning struct literal
}

///| Method: receiver `self : Self`, last expression is the return value
fn Point::manhattan(self : Self) -> Int {
  self.x.abs() + self.y.abs()
}

///| Call site: a declared constructor is called by the *type name*, not `Type::Type`
let p = Point(1, 2)

///| Constructors are ordinary functions, so they take labelled and optional
/// arguments and may raise:
fn Endpoint::Endpoint(host : String, port? : Int = 80) -> Endpoint {
  Host(host, port)
}
let e = Endpoint("example.com", port=443)

コンストラクターを宣言しなくても、構造体はレコードリテラル Point::{ x: 1, y: 2 }(型がわかっている場合は { x: 1, y: 2 } でも可)で構築できます。構築時に検証、デフォルト値、または短い呼び出し形式が必要な場合は fn Type::Type(..) を宣言し、その後はすべての呼び出し箇所で Point(1, 2) を使うことをおすすめします。

複数行テキスト
let text =
  #|line 1
  #|line 2
コメントとブロック区切り

///| はブロック区切りです。/// コメントは、後続する ///| ブロックに付与されます。

///|
/// This function is foo
fn foo() -> Unit { ... }

///|
/// This function is bar
fn bar() -> Unit { ... }

ファイル先頭で ///| を連続させないでください。別々のブロックが作成されます。

スナップショットテスト

moon test -u は inspect(val)/debug_inspect(val) の空の content="" を自動で埋めます。使い分け(v0.9.2 以降):

  • プリミティブ型/文字列 → inspect(ここでは Show で問題ありません)
  • コンテナー型(Array/Map/Set/Option/Result/タプル)およびカスタム型 → debug_inspect。これらのコンテナーでの Show は非推奨であり、Debug なら構造化されたインデント付きの出力が得られるためです。Show は人間向けのフォーマットにのみ使用してください。
  • 深くネストされた構造 → @json.inspect。フォーマット後も JSON 出力の可読性が保たれます。derive(ToJson) が必要です。

実行時の値ではなく、検査する式の静的型に基づいて選択してください。Result[Int, _] は Ok(123) を保持している場合でもコンテナーです。また、このルールは再帰的に適用されます。カスタム型のスナップショット作成やアサートを行うには(コンテナー内にネストされている場合も含め)、そのカスタム型に derive(Debug) が必要です。また、@debug.assert_eq による等価性チェックには derive(Eq) も必要です。

test "snapshot" {
  inspect("hello", content="")          // primitive/string -> inspect
  debug_inspect([1, 2, 3], content="")  // container -> debug_inspect
}

///| Custom type: derive Debug to snapshot, derive Eq to assert_eq
struct P {
  x : Int
} derive(Eq, Debug)

test "custom type" {
  debug_inspect(P::{ x: 1 }, content="")
  @debug.assert_eq(P::{ x: 1 }, P::{ x: 1 })
}

moon test -u の実行後:

test "snapshot" {
  inspect("hello", content="hello")
  debug_inspect([1, 2, 3], content="[1, 2, 3]")
}

content="" と記述し、moon test -u に埋めてもらってください。期待される文字列を手書きしないでください。Debug のレンダリングは構造を反映し、Show とは異なるため、推測した値では初回実行時にテストが失敗します。ツールチェーンを実行できない場合は、すべての content="" を空のままにして、その旨を伝えてください。空のスナップショットは正直な TODO ですが、誤った値は誤ったアサーションになります。

以下は Debug が生成する形式です。既存のスナップショットを読むために示しています。新しいものを書くためではありません。書き込みは引き続き -u の役目です。

検査対象の値debug_inspect の出力
[1, 2, 3][1, 2, 3]
Point::{ x: 1, y: 2 }(構造体){ x: 1, y: 2 }
Shape::Rect(width=3, height=4)(列挙型、ラベル付き)Rect(width=3, height=4)
Host("example.com", 443)(列挙型、位置指定)Host("example.com", 443)
Err(InvalidEof)(Result と suberror)Err(InvalidEof)

長い値は折り返されてインデントされます。これも -u に書き込ませるべき理由のひとつです。

プロパティテスト(v0.10.9)

QuickCheck は標準ライブラリの moonbitlang/core/quickcheck(別名 @quickcheck)に含まれるようになりました。moonbitlang/quickcheck を別途依存関係に追加しないでください。エントリーポイントは 2 つあります。

  • @quickcheck.check(property) — 失敗時は例外を発生させ、成功時は何も出力しません。テスト対象の型には Arbitrary + Shrink + Debug が必要です。
  • @quickcheck.report(property) — 例外を発生させる代わりに、構造化された QuickCheckReport を返します。必要なのは Arbitrary + Shrink のみです。
test "reverse is an involution" {
  @quickcheck.check((xs : Array[Int]) => xs.rev().rev() == xs)
}

derive(Arbitrary) と derive(Shrink) は、カスタム型のインスタンスを生成します。実行は省略可能なラベル付き引数で調整できます。count?、max_size?、max_shrinks?、seed?(失敗を再現するためのシード)、filter? と discard_ratio?、統計情報用の observe?(@quickcheck.{label, classify, collect} 経由)です。

ドキュメントテスト

.mbt.md ファイルまたは ///| インラインコメントで利用できます。

コードブロック動作
```mbt checkLSP によるチェック
```mbt testtest {...} として実行
```moonbit表示のみ(実行されません)

例(インラインコメント):


///|
/// Increment an integer by 1
/// ```mbt test
/// inspect(incr(41), content="42")
/// ```
pub fn incr(x : Int) -> Int {
  x + 1
}

リリース前チェックリスト

リリース前に実行します。

moon fmt   # Format code (also migrates deprecated syntax and JSON config)
moon info  # Generate type definition files

pkg.generated.mbti は moon info によって自動生成されます。直接編集しないでください。

組み込み型のメソッドを調べる

moon ide doc StringView   # StringView methods
moon ide doc Array        # Array methods
moon ide doc Map          # Map methods

クイックリファレンス

トピックコマンド詳細
テストmoon testhttps://docs.moonbitlang.com/en/stable/language/tests
スナップショットを更新moon test -u上記と同じ
テストを絞り込むmoon test --filter 'glob'特定のテストを実行
ベンチマークmoon benchhttps://docs.moonbitlang.com/en/stable/language/benchmarks
ドキュメントテストmoon check / moon testhttps://docs.moonbitlang.com/en/stable/language/docs
フォーマットmoon fmtmoon.mod.json/moon.pkg.json を DSL に移行し、trait/impl に fn キーワードを追加します。落とし穴のリストにあるその他の項目は対象外です — try?、fn new、is-main、extend はすべて手動で対応します
型を生成moon info-
エラーコードを解説moon explain E0087または moon check --explain
ドキュメントリファレンスmoon ide doc <Type>moon doc <SYMBOL> は非推奨です
レジストリを検索moon search <keyword>機械可読な出力には --json を指定します
レジストリツールを実行moonx author/mod/cmd/tool非推奨の moon runwasm に代わるコマンドです
証明をチェックmoon proveWhy3 を別途インストールする必要はありません(v0.10.9)
ワークスペースを初期化moon work initreference/configuration.md を参照してください
ワークスペースに追加moon work use mod1 mod2モジュールをワークスペースに追加します
API 使用状況を分析moon ide analyze .公開 API の使用状況統計を表示します

moon ide ツール

コードナビゲーションでは grep より正確です。詳細は reference/ide.md を参照してください。 サブコマンドは peek-def、find-references、outline、doc、hover、 rename、analyze です。goto-definition はありません。

# Show symbol definition (global search by symbol syntax)
moon ide peek-def Parser::read_u32_leb128

# Package outline
moon ide outline .

# Find references
moon ide find-references TranslationUnit

# Peek a definition from a source location (contextual, substring match)
moon ide peek-def Parser -loc src/parse.mbt:46:4

# Show type signature and docs at a location (unlike peek-def, shows inferred type + doc comments)
moon ide hover my_func -loc src/lib.mbt:10:4

# Rename symbol across the project
moon ide rename old_name new_name

# Analyze public API usage
moon ide analyze .              # Current package
moon ide analyze internal/*     # Glob pattern

関数型 for ループ

可能な場合は、関数型 for ループを優先してください。より読みやすく、動作を把握しやすくなります。

// Functional for loop with state
for i = 0, sum = 0; i <= 10 {
  continue i + 1, sum + i  // Update state
} nobreak {
  sum  // Value at loop exit (nobreak, not else)
}

// Range for (recommended)
for i in 0..<n { ... }
for i, v in array { ... }  // index and value

// Range for carrying extra loop state (v0.10.0)
for x in xs; sum = 0; sum = sum + x { ... }

// Infinite loop (for { } is deprecated)
for ;; { ... }

ループにラベルが付いている場合、その中の break / continue にもラベルを指定する必要があります。ラベル付きループ内でラベルなしのものを直接使うことは非推奨です。

ラベル付きブロック(v0.10.9)

ブロックにはラベルを付けられます。break label~ value は、その値を伴ってブロックを抜けます。関数から戻らずに、ネストされた制御フローから抜ける場合に使用します。

fn absolute(n : Int) -> Int {
  result~: {
    if n < 0 {
      break result~ -n
    }
    n
  }
}

ラベルなしの break でラベル付きブロックを抜けることはできません。また、continue の対象はループのみです。ブロックとループのラベルは同じ名前空間を共有します。外側のラベルを再利用すると、そのラベルがシャドーイングされ、E0036 が報告されます。

イテレーターとリスト内包表記

[for .. => body] は配列を構築します。フィルタリングするには、=> の前に if <guard> を追加します。 結果の型は期待される型に従います。デフォルトでは Array ですが、 FixedArray、ReadOnlyArray、String、Bytes、Json にもなります。

let evens = [for i in 0..<100 if i % 2 == 0 => i]
let labelled = [for i, x in ["a", "b", "c"] => "\{i}: \{x}"]
let text : String = [for x in 0..<3 => (x + 'a').unsafe_to_char()]

内包表記内では return / break / continue は使用できません。

明示的な [| .. |] リテラルで Iter を構築します(v0.10.4) — 期待される型によって [x, ..xs] が Iter に変換されることに依存する方法は、評価順序を 暗黙のうちに変更するため、非推奨です。

let prefix = [| 1, 2, 3 |]
let values = [| ..prefix, 4, 5 |]        // ..xs evaluated now, consumed lazily
let squares = [| for x in 1..<=3 => x * x |]  // fully lazy

遅延シーケンスや無限シーケンスで状態を引き継ぐ場合は、Iter を直接構築します。

let mut p1 = 1
let mut p2 = 0
let fibs : Iter[Int] = Iter::new(fn() {
  let next = p1
  p1 = p1 + p2
  p2 = next
  Some(next)
})
let first_six = fibs.take(6).collect()

イテレーターは単一パスです。each / fold / collect で消費した後は、ソースから新しいイテレーターを取得してください。

テンプレート書き込み <+(v0.10.0)

buf <+ expr は StringBuilder に追加します。write_string より簡潔で、 マークアップや文字列の構築では $| 補間と相性がよく、buf <? expr は条件付きで書き込みます。

let buf = StringBuilder()
buf <+ "<ul>"
for item in items {
  buf <+ $|<li>\{item}</li>
}
buf <+ "</ul>"
buf.to_string()

バイト列も補間をサポートします。let b : Bytes = b"value=\{x}"(UTF-8 エンコード)。

正規表現:=~、lexmatch、lexscan

必要な処理に応じて、次の3つの構文を使い分けます。

必要な処理構文
1つの真偽値チェックinput =~ re"abc"
異なる値を返す複数の正規表現ケースlexmatch input with longest { ... }
カーソルを進めながらストリームをトークン化する@lexbuf.{Lexbuf, AsyncLexbuf, StringScanner} に対する lexscan
///| Boolean search (first-match, search-based — anchor with ^ / $ if needed)
let matched = s =~ re"abc"

///| Bind the match and the surrounding text
if s =~ (re"[0-9]+" as digits, before~, after~) { ... }

///| Multi-case dispatch over a String / StringView
let kind = lexmatch input with longest {
  (re"^[ \t\r\n]+") => Space
  (re"^[A-Za-z][A-Za-z0-9_]*" as t) => Ident(t)
  _ => Other
}

右辺には正規表現の定数式(リテラル、const、または + / | /括弧で組み合わせた定数)を指定する必要があります。実行時文字列は拒否されます。 旧式の実験的な文字列ピース lexmatch パターンと lexmatch? は削除されました。lexscan は v0.10.9 で安定版になりました。

文字列定数

const は文字列の連結と補間をサポートします(v0.8.3 以降)。

const Hello : String = "Hello"
const HelloWorld : String = Hello + " world"
const Message : String =
  $|========
  $|\{HelloWorld}
  $|========

エラー処理

MoonBit はチェック付きエラーを使用します。詳細は reference/ffi.md を参照してください。

宣言形式は3つあります。用途に合う最小のものを選んでください。

///| No payload at all (the common case for a single failure mode).
/// This declares an error type `EmptyInput` with one payload-free constructor
/// of the same name — `raise EmptyInput` works directly.
suberror EmptyInput

///| `derive` attaches to the payload-free form too, with no braces in between
suberror NotFound derive(Eq, Debug)

///| One constructor carrying a payload — the constructor repeats the type name
suberror DivError { DivError(String) }

///| Several constructors, like an enum
suberror ParseError {
  InvalidEof
  InvalidChar(Char)
}

可視性の指定は他と同様です。pub suberror / pub(all) suberror / priv suberror。

スナップショットを取る、または表示する可能性のあるエラー型には、すべて Debug を導出してください。 以下の catch 形式が返す Result はコンテナーなので、debug_inspect にはエラーを含む両方の側で Debug が必要です。

suberror ParseError {
  InvalidEof
  InvalidChar(Char)
} derive(Eq, Debug)
///| Declare with raise, auto-propagates
fn parse(s: String) -> Int raise ParseError {
  if s.is_empty() { raise ParseError::InvalidEof }
  ...
}

///| Convert to Result — `try?` was removed (v0.10.0)
let result : Result[Int, ParseError] = Ok(parse(s)) catch { e => Err(e) }

///| Single expression with a fallback: omit `try`
let n = parse(s) catch { _ => 0 }

///| Panic instead of handling
let n = try! parse(s)

///| Full form: `noraise` branch handles the success value
let n = try parse(s) catch {
  ParseError::InvalidEof => -1
  _ => 0
} noraise {
  v => v
}

この変換結果のスナップショットを取る場合(合成ケース):

test "parse failure" {
  let r : Result[Int, ParseError] = Ok(parse("")) catch { e => Err(e) }
  debug_inspect(r, content="")   // moon test -u fills in Err(InvalidEof)
}

束縛に具体的なエラー型(Result[Int, Error] ではなく Result[Int, ParseError])を注釈すると、エラー側には Debug インスタンスがあり、スナップショットが機能します。単独の Error は、derive を付与できないオープン型です。独自の suberror にシグネチャを絞るか、エラーをマッチさせて分岐側のスナップショットを取ってください。

クリーンアップ:defer と errdefer(v0.10.9)
///| defer: always runs on leaving the body (return / break / continue / raise)
fn read_all(path : String) -> String raise {
  let f = open(path)
  defer f.close()
  f.read_to_string()
}

///| errdefer: runs only when the body raises or is cancelled.
/// Use it when the resource is handed to the caller on success.
async fn connect(addr : Addr) -> Tcp {
  let sock = make_tcp_socket()
  errdefer sock.close()
  connect_tcp_socket(sock, addr)
  sock
}

連続する defer は逆順に実行されます。右辺では return / break / continue は使用できません。クリーンアップ自体がエラーを発生させる場合(非同期コードでは await する場合もあります)、そのエラーが元のエラーに置き換わります。エラーを発生させられない本体内の errdefer は E0091 を報告します。クリーンアップのみを行ってエラーを再発生させる catch-all は、errdefer に置き換える必要があります(E0092)。

Guard(v0.10.9)

guard は網羅性解析を実行します。失敗する可能性のある guard には else が必要です。コンパイラーが常に成功すると証明できない場合は E0087 を報告します。終了させることが意図されている場合は guard! を使用してください。guard! に else は指定しません。

///| Fallible guard: `else` supplies the value for the whole enclosing function
fn guarded_get(array : Array[Int], index : Int) -> Int? {
  guard index >= 0 && index < array.length() else { None }
  Some(array[index])
}

///| With a pattern, via `is`
fn plain_text(r : Resource) -> String raise Error {
  guard r is PlainText(text) else { fail("not plain text") }
  text
}

///| Provably total, or a deliberate panic: `guard!` takes no `else`
fn require_some(value : Int?) -> Int {
  guard! value is Some(result)
  result
}

網羅的な条件に対する冗長な ! や else は、警告されるようになりました。

CI と公開

サードパーティ製アクション

hustcer/setup-moonbit@v1 などのサードパーティ製アクションより、公式の curl インストーラー(assets/ci.yaml)または moonbit-overlay を使った Nix(assets/ci-nix.yaml)を優先してください。インストーラーは短く、アクションのサプライチェーンに何も追加しません。flake.nix が存在する場合、Nix の方法は完全に再現可能です。

moon update は必須です

ランナーは空のレジストリインデックスで起動します。レジストリでホストされている MoonBit 依存関係があると、moon update を実行するまで moon check / moon test / moon build は Failed to resolve registry dependency で失敗します。mooncakes を扱うすべてのワークフローで、CLI のインストール直後に実行してください。

ビルドが MoonBit に依存する npm パッケージの公開

プロジェクトによっては(Vite プラグインや言語ツールなど)、npm パッケージとして公開しながら、pnpm build 内で moon を実行します。たとえば、moon -C tools/parser build --release --target js を呼び出す pnpm build:parser などです。この場合、npm の公開ワークフローでは pnpm build の前に MoonBit CLI と moon update の両方をセットアップする必要があります。そうしないと、CI でビルドが失敗します。

OIDC Trusted Publishing(NPM_TOKEN 不要)を使用し、MoonBit を適切にセットアップする、リリースをトリガーとした最小限の公開ワークフローについては、assets/publish-to-npm.yaml を参照してください。手動での npm publish の実行を避けるため、リリース自動化ツールと組み合わせてください(release-please と OIDC のセットアップについては npm-release スキルを参照)。

mooncakes.io への公開

パッケージの内容は .moonignore(ない場合は .gitignore)でフィルタリングされます。 ドットで始まるファイルと _build/ はデフォルトで除外されます。mooncakes.io は、大文字と小文字だけが異なる既存のパッケージ名を拒否します。公開前にアーティファクトを確認してください。

moon publish --dry-run -v
unzip -l _build/publish/<author>-<module>-<version>.zip

アセット

  • assets/moon.mod — DSL 形式のモジュール設定サンプル(moon.mod.json の代替)
  • assets/moon.pkg — DSL 形式のパッケージ設定サンプル(moon.pkg.json の代替)
  • assets/ci.yaml — GitHub Actions CI(curl インストーラー)
  • assets/ci-nix.yaml — Nix を使用した GitHub Actions CI(moonbit-overlay)
  • assets/publish-to-npm.yaml — MoonBit ビルドと OIDC Trusted Publishing を使用した、リリースをトリガーとする npm 公開
Nix のセットアップ(moonbit-overlay)

moonbit-community/moonbit-overlay は、再現可能な MoonBit ビルドのための Nix フレークオーバーレイを提供します。

MoonBit プロジェクト用の最小限の flake.nix:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    moonbit-overlay.url = "github:moonbit-community/moonbit-overlay";
    moon-registry = {
      url = "git+https://mooncakes.io/git/index";
      flake = false;
    };
  };

  outputs = { nixpkgs, moonbit-overlay, moon-registry, ... }:
    let
      system = "x86_64-linux"; # or aarch64-darwin, etc.
      pkgs = import nixpkgs {
        inherit system;
        overlays = [ moonbit-overlay.overlays.default ];
      };
      moonHome = pkgs.moonPlatform.bundleWithRegistry {
        cachedRegistry = pkgs.moonPlatform.buildCachedRegistry {
          moonModJson = ./moon.mod.json;
          registryIndexSrc = moon-registry;
        };
      };
    in {
      devShells.${system}.default = pkgs.mkShellNoCC {
        packages = [ moonHome pkgs.git ];
        env.MOON_HOME = "${moonHome}";
      };
    };
}

オーバーレイの主な API:

  • pkgs.moonPlatform.buildMoonPackage - MoonBit パッケージを Nix の派生物としてビルドします
  • pkgs.moonPlatform.bundleWithRegistry - キャッシュ済みレジストリを含む MOON_HOME を作成します
  • pkgs.moonPlatform.buildCachedRegistry - mooncakes レジストリの依存関係を事前に取得します

オーバーレイの moonModJson 引数は、引き続き JSON マニフェストを想定しています。 モジュールを moon.mod に移行した場合は、Nix 用に生成済みの moon.mod.json を保持するか、 それを削除する前に、オーバーレイが DSL をサポートしているか確認してください。

コメント