🐇

Moonbit moon cli の便利機能達 - 組み込み lint, bench, snapshot, coverage, doc

に公開

MoonBit の魅力の一つが moon コマンドの便利なコマンドです。

moonbit がインストールされていれば、moon -h で一覧を確認できますが、使ってみるまで何をやってくれるか期待値が難しいと思うので、自分が特に便利と思った機能を紹介します。

現状ライブラリが不足していても、これだけでライブラリの不足を補えるパワーがあります。

tl;dr

  • moon lint による型チェックと lint
  • moon run によるバックエンド指定実行
  • moon test による doc test とスナップショットテスト
  • moon info による型定義生成
  • moon doc でビルトインの型とメソッドの検索
  • moon bench によるベンチマーク
  • moon coverage analyze
  • moon ide で LSP のシンボル検索を直接使う

moon check による型チェックと警告

moonbit では現状 Lint という概念がなく、型チェックとlintが一体化しています。デフォルトでもかなり強めに 警告が出ます。
例えば未使用変数や未使用の型引数でも lint が効きます。

❯ moon check            
Warning: [0053]
    ╭─[ /Users/mizchi/mizchi/luna.mbt/src/astra/tree/builder.mbt:20:12 ]
    │
 20 │ pub fn[F : @env.FileSystem] build_document_tree_with_fs(
    │            ───────┬───────  
    │                   ╰───────── Warning (unused_trait_bound): This trait bound is unused.
────╯
Finished. moon: ran 419 tasks, now up to date (1 warnings, 0 errors)

--deny-warn をつけると warning がエラー扱いになり、ステータスコードも 1 を返します。CIで使います。

Failed with 0 warnings, 1 errors.
error: failed when checking project

moon.mod.json または moon.pkg.json で"warn-list"で抑制できます。

{
  "warn-list": "-unused_trait_bound"
}

- が削除で、+ がデフォルトで有効でないものを追加できます。 一覧は moonc build-package -warn-help で取得できます。

(最近まで warn と alert が別の概念だったんですが、統合されました)

moon run --target でターゲットを指定

moonbit ではこの条件を満たすことで moon run コマンドでプログラムを実行できます。

  • moon.mod.json の配下にある
  • ディレクトリの moon.pkg.json が "is-main": true である
  • ディレクトリのいずれかの .mbt が fn main {} を持つ
moon run main.mbt
moon run .
moon run cmd/main.mbt

この時、 --target でビルドターゲットを選ぶことができます。 all だと全て(experimental な llvm を除く)で実行します。

moon run main.mbt --target wasm-gc
moon run main.mbt --target js
moon run main.mbt --target native
moon run main.mbt --target all

moon test

run と同じくターゲット指定ができます。最小のテストを置いて確かめてみましょう

test {
  assert_true(true)
}

ターゲットを指定して実行できます。moon run と同じく--all が使えます。

moon test --target js
moon test --target native
moon test --target all

また、コードのコメントに対する doc test と、 .mbt.md に対する doc test があります。ドキュメントにコード例を書いて、それをテストとして実行できます。

.mbt.md ファイルか、///| インラインコメント内で使えます。

コードブロック 動作
```mbt check LSP で検査される
```mbt test test {...} として実行
```moonbit 何も実行されない(表示のみ)

インラインコメントでの例:

///|
/// 整数を1増やす
/// ```mbt test
/// inspect(incr(41), content="42")
/// ```
pub fn incr(x : Int) -> Int {
  x + 1
}

ドキュメントとテストが一体化するので、サンプルコードの陳腐化を防げます。

moon test 組み込みの snapshot test

ビルトインの inspect 関数と moon test -u の組み合わせが便利です。

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

moon test -u を実行すると、content="" の部分が自動で補完されます。

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

テストを書くときは、まず content="" で書いておいて、実行結果を確認しながら更新していくのが楽です。

moon coverage analyze

テストで実行されていないカバレッジレポートが取れます。

(TODO: あとでサンプルを載せる)

moon doc による組み込み型とメソッドの検索

moon doc コマンドで型のメソッド一覧を確認できます。

moon doc StringView   # StringView のメソッド一覧
moon doc Array        # Array のメソッド一覧
moon doc Map          # Map のメソッド一覧

標準ライブラリのソースを読まなくても型シグネチャがわかるので、何ができるかわかります。

例えば StringView という String のスライスを不変に参照する機能があります。これは巨大な文字列をスライスしたものをString型と似たAPIで参照できる機能です。

❯ moon doc StringView   
package "moonbitlang/core/string"

type StringView

  pub fn StringView::add(Self, Self) -> Self
  pub fn StringView::at(Self, Int) -> UInt16
  pub fn StringView::char_length(Self) -> Int
  pub fn StringView::char_length_eq(Self, Int) -> Bool
  pub fn StringView::char_length_ge(Self, Int) -> Bool
  pub fn StringView::code_unit_at(Self, Int) -> UInt16
  pub fn StringView::compare(Self, Self) -> Int
  pub fn StringView::contains(Self, Self) -> Bool
  pub fn StringView::contains_any(Self, chars~ : Self) -> Bool
  pub fn StringView::contains_char(Self, Char) -> Bool
  pub fn StringView::data(Self) -> String
  pub fn StringView::default() -> Self
  pub fn StringView::equal(Self, Self) -> Bool
  pub fn StringView::find(Self, Self) -> Int?
  pub fn StringView::find_by(Self, (Char) -> Bool) -> Int?

他の言語にない機能なので使い方をAIに教えないといけないですが、これがあることをエージェントに教えておくと人間がリファレンスを探す手間が減ります。

moon bench によるベンチマーク

moon bench でベンチマークを実行できます。

書き方の例

test "fib benchmark" (b : @bench.Test) {
  b.bench(fn() { fib(20) })
}

b : @bench.Test パラメータを受け取る test ブロックがベンチマークとして認識されます。

副作用のない計算はコンパイラに最適化で消されてしまう可能性があるので、b.keep() で結果を保持します。

test "sum benchmark" (b : @bench.Test) {
  let result = b.bench(fn() {
    let mut sum = 0
    for i = 0; i < 1000; i = i + 1 {
      sum = sum + i
    }
    sum
  })
  b.keep(result)
}

実行すると統計情報が出力されます。

test fib benchmark ... bench result
time (mean ± σ)       range (min … max)
21.67 µs ± 0.54 µs    21.28 µs … 23.14 µs    in 10 × 4619 runs

平均時間、標準偏差、最小・最大値、実行回数がわかります。

markdown コンパイラの実装でも大活躍しました。

https://zenn.dev/mizchi/articles/markdown-incremental-preview

気軽にベンチマークを取れることがパフォーマンス意識を向上させてくれる、という肌感があり、とても良いです。

moon ide で直接 IDE のシンボル検索を使う

なんとコマンドから シンボル検索と定義ジャンプの LSP の機能が直接使えます。

これはオンラインミートアップの作者のデモで知っていて、まだドキュメントがありませんが、コマンドとしては存在していました。

moon ide find-references

❯ moon ide find-references -query "normalize_path"
`fn normalize_path` in package mizchi/luna/sol/router at /Users/mizchi/mizchi/luna.mbt/src/sol/router/sol_routes.mbt:270-297:
    ^^^^^^^^^^^^^^

Found 9 references of this symbol:
/Users/mizchi/mizchi/luna.mbt/src/sol/router/sol_routes.mbt:272:4-272:18:
    | 
    | ///|
    | /// Normalize path (remove duplicate slashes, trailing slash)
272 | fn normalize_path(path : String) -> String {
    |    ^^^^^^^^^^^^^^
    |   if path == "" || path == "/" {
    |     return "/"

moon ide goto-definition -query

$ moon ide goto-definition -query="normalize_path"
There are total 2 symbols match query 'normalize_path':
Top 2 best-matching symbols:

`fn normalize_path` in package mizchi/luna/sol/router at /Users/mizchi/mizchi/luna.mbt/src/sol/router/sol_routes.mbt:270-297:
    ^^^^^^^^^^^^^^
270 | ///|
    | /// Normalize path (remove duplicate slashes, trailing slash)
    | fn normalize_path(path : String) -> String {
    |   if path == "" || path == "/" {
    |     return "/"
    |   }
    |   let chars = path.to_array()
    |   let result : Array[Char] = []
    |   let mut prev_slash = false

あと moon ide peek-def というコマンドがあるっぽいんですが、ドキュメントがないので使い方が分かりませんでした。moon ide peek-def -loc ./src/luna/routes/compile.mbt:6:4 -symbol normalize_path のように使うようです。

おわり

どうでしょうか。TypeScript の eslint や vitest, cargo の criterion に似たような機能はありますが、言語組み込みの CLI でここまで機能が充実している言語は、自分は他に知りません。この機能の豊富さで、ライブラリが足りなくてもなんとかなります。

AI想定と思われる moon doc や moon ide が生えてるのも面白いですね。

まあ若い言語なので当然使ってるとバグがあり、特定のバックエンド+特定の機能の組み合わせで発生しがちです。

そういうのを見つけたらここに報告してください。自分は5件ぐらい、discord で直接いうので7件ぐらい報告しています。

https://github.com/moonbitlang/moonbit-docs/issues

肌感、クリティカルなものは来週の火曜日ぐらいに治ります。

Discussion