初心者向け MoonBit ツアー#

このガイドは初学者向けであり、5 分で終わる駆け足のツアーではありません。MoonBit が可能にする、よりモダンで関数型的な書き方にまだ親しみのない人に向けて、簡潔で分かりやすい解説を目指しています。

すぐに言語仕様そのものを学びたい場合は、言語の概要 を参照してください。

インストール#

拡張機能

現在、MoonBit の開発サポートは VS Code 拡張機能を通じて提供されています。VS Code Marketplace にアクセスして、MoonBit の言語サポートをダウンロードしてください。

ツールチェーン

(推奨)上の拡張機能をインストール済みであれば、アクションメニューで Install moonbit toolchain を実行するだけでランタイムを直接インストールできるため、この節は飛ばせます。ランタイムのインストール

インストールスクリプトも用意しています。Linux と macOS では次の方法でインストールできます:

curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash

Windows ユーザーは PowerShell を使います:

Set-ExecutionPolicy RemoteSigned -Scope CurrentUser; irm https://cli.moonbitlang.com/install/powershell.ps1 | iex

これにより、MoonBit は $HOME/.moon に自動的にインストールされ、PATH に追加されます。

インストール後に moon が見つからない場合は、環境変数を反映させるためにターミナルまたは VS Code を再起動してみてください。

現時点の MoonBit はまだ本番利用向けではなく、活発に開発が進められています。MoonBit を更新するには、上記のコマンドをもう一度実行してください。

moon help を実行すると多くのサブコマンドが表示されます。ただし、ここで必要になるのは buildrunnew だけです。

プロジェクト(より正確にはモジュール)を作成するには、moon new <path> を実行します。ここで path はプロジェクトを配置したい場所です。例えば moon new examine を実行すると、次のようになります:

examine
├── Agents.md
├── cmd
│   └── main
│       ├── main.mbt
│       └── moon.pkg
├── LICENSE
├── moon.mod.json
├── moon.pkg
├── examine_test.mbt
├── examine.mbt
├── README.mbt.md
└── README.md -> README.mbt.md

この中には cmd/main ライブラリがあり、そこにはプログラムの入口となる fn main が含まれています。cd examine && moon run cmd/main を実行してみてください。

このチュートリアルでは、プロジェクト名を examine、現在の作業ディレクトリも examine と仮定します。

例: テストに合格した学生を探す#

この例では、複数の学生の点数が与えられたとき、何人がテストに合格したかを求めてみます。

そのために、まずデータ型を定義し、必要な関数を洗い出して、テストを書きます。その後で関数を実装します。

特に断りがない限り、以下の定義は examine.mbt ファイルに記述します。

データ型#

MoonBit の 基本データ型 には次のものがあります:

  • Unit

  • Bool

  • Int, UInt, Int64, UInt64, Byte, ...

  • Float, Double

  • Char, String, ...

  • Array[T], ...

  • タプルなど

学生 ID と点数を持つ構造体をプリミティブ型で表すには、学生 ID(型は String)と点数(型は Double)からなる 2 要素タプル (String, Double) を使えます。しかし、この表現は直感的ではありません。例えば、学生 ID と身長を持つ構造体のような他の型と区別できないからです。

そこで、struct を使って独自のデータ型を宣言します:

struct Student {
  id : String
  score : Double
}

試験は合格か不合格かのどちらかなので、判定結果は enum を使って次のように定義できます:

enum ExamResult {
  Pass
  Fail
}

関数#

関数 とは、入力を受け取り、結果を返すコード片です。

この例では、学生が試験に合格したかどうかを判定する必要があります:

fn is_qualified(student : Student, criteria: Double) -> ExamResult {
  ...
}

この関数は、先ほど定義した Student 型の値 student と、科目や国ごとに基準が異なり得るため Double 型で表した基準値 criteria を受け取り、ExamResult を返します。

... 構文を使うと、関数をいったん未実装のままにできます。

また、試験に合格した学生が何人いるかを求める必要があります:

fn count_qualified_students(
  students : Array[Student],
  is_qualified : (Student) -> ExamResult
) -> Int {
  ...
}

MoonBit では関数は第一級の値として扱われます。つまり、関数を変数に束縛したり、引数として渡したり、戻り値として受け取ったりできます。この関数は、学生の構造体の配列と、各学生が合格したかどうかを判定する別の関数を受け取ります。

テストを書く#

関数の期待される振る舞いを定義するために、インラインテストを記述できます。これは、プログラムをリファクタリングしたときに回帰が起きないことを確かめるのにも役立ちます。

test "is qualified" {
  assert_eq(is_qualified(Student::{ id : "0", score : 50.0 }, 60.0), Fail)
  assert_eq(is_qualified(Student::{ id : "1", score : 60.0 }, 60.0), Pass)
  assert_eq(is_qualified(Student::{ id : "2", score : 13.0 }, 7.0), Pass)
}

ExamResultShowEq が実装されていないことを示すエラーメッセージが出ます。

ShowEqtrait です。MoonBit の trait は、ある型が提供すべき共通操作を定義します。

例えば Eq では、同じ型の 2 つの値を op_equal という関数で比較できることを定義します:

trait Eq {
  op_equal(Self, Self) -> Bool
}

一方 Show では、値を String に変換するか、Logger を使って出力できることを定義します:

trait Show {
  output(Self, &Logger) -> Unit
  to_string(Self) -> String
}

そして assert_eq はこれらの trait を使って、渡された 2 つの値を比較し、等しくない場合に表示できるよう、型パラメータに制約を付けています:

fn assert_eq![A : Eq + Show](value : A, other : A) -> Unit {
  ...
}

ExamResult に対して EqShow を実装する必要があります。方法は 2 つあります。

  1. 明示的な実装を定義する方法です:

    impl Eq for ExamResult with equal(self, other) {
      match (self, other) {
        (Pass, Pass) | (Fail, Fail) => true
        _ => false
      }
    }
    

    ここでは パターンマッチング を使って ExamResult の各ケースを判定しています。

  2. もう 1 つの方法は derive を使うことです。EqShow組み込み trait であり、ExamResult の出力はかなり単純です:

    enum ExamResult {
      Pass
      Fail
    } derive(Show)
    

trait を実装できたので、テストの実装を続けます:

test "count qualified students" {
  let students = [
    { id: "0", score: 10.0 },
    { id: "1", score: 50.0 },
    { id: "2", score: 61.0 },
  ]
  let criteria1 = fn(student) { is_qualified(student, 10) }
  let criteria2 = fn(student) { is_qualified(student, 50) }
  assert_eq(count_qualified_students(students, criteria1), 3)
  assert_eq(count_qualified_students(students, criteria2), 2)
}

ここでは ラムダ式 により、先ほど定義した is_qualified を再利用して異なる基準を作成しています。

moon test を実行して、テストが成功するかどうかを確認できます。

関数を実装する#

is_qualified 関数は、単純な比較で実装できます:

fn is_qualified(student : Student, criteria : Double) -> ExamResult {
  if student.score >= criteria {
    Pass
  } else {
    Fail
  }
}

MoonBit では、最後の式の結果が関数の戻り値になり、if 式では各分岐の結果がその式の値になります。

count_qualified_students 関数では、各学生が合格したかどうかを調べるために配列を走査する必要があります。

素朴な方法としては、可変変数と for ループ を使います:

fn count_qualified_students(
  students : Array[Student],
  is_qualified : (Student) -> ExamResult
) -> Int {
  let mut count = 0
  for i = 0; i < students.length(); i = i + 1 {
    if is_qualified(students[i]) == Pass {
      count += 1
    }
  }
  count
}

しかし、これは境界チェックが発生するため効率的でもなく、直感的でもありません。そこで for ループを for .. in ループ に置き換えられます:

fn count_qualified_students(
  students : Array[Student],
  is_qualified : (Student) -> ExamResult
) -> Int {
  let mut count = 0
  for student in students {
    if is_qualified(student) == Pass { count += 1}
  }
  count
}

さらに別の方法として、イテレータ 向けに定義された関数を使うこともできます:

fn count_qualified_students(
  students : Array[Student],
  is_qualified : (Student) -> ExamResult
) -> Int {
  students.iter().filter(fn(student) { is_qualified(student) == Pass }).count()
}

これで、先ほど定義したテストは通るはずです。

ライブラリを公開する#

最初の MoonBit ライブラリの完成、おめでとうございます。

これを他の開発者と共有すれば、同じことを繰り返し実装する必要がなくなります。

ただし、その前にもう少しやることがあります。

可視性を調整する#

自分たちのプログラムが他の人からどう使われるかを確認するために、MoonBit には ブラックボックステスト という仕組みがあります。

先ほど定義した test ブロックを、新しいファイル top_test.mbt に移してみましょう。

すると、次のようなエラーが出ます:

  • is_qualifiedcount_qualified_students が未束縛になっている

  • FailPass が未定義になっている

  • Student が struct 型ではなく、フィールド id が見つからない、など

これらはすべて可視性の問題に由来します。デフォルトでは、定義した関数は現在のパッケージ(現在のフォルダで区切られる範囲)の外部からは見えません。また、型はデフォルトで抽象型として扱われるため、Student 型と ExamResult 型が存在することしか分かりません。ブラックボックステストを使えば、他の人に公開したいものに、意図した可視性が正しく付いているかを確認できます。

他の人が関数を使えるようにするには、fn の前に pub を付けて関数を公開する必要があります。

他の人が型を構築して内容を読めるようにするには、structenum の前に pub(all) を付けて型を公開する必要があります。

また、count qualified students のテストに型注釈を追加するため、少し修正する必要があります:

test "count qualified students" {
  let students: Array[@examine.Student] = [
    { id: "0", score: 10.0 },
    { id: "1", score: 50.0 },
    { id: "2", score: 61.0 },
  ]
  let criteria1 = fn(student) { @examine.is_qualified(student, 10) }
  let criteria2 = fn(student) { @examine.is_qualified(student, 50) }
  assert_eq(@examine.count_qualified_students(students, criteria1), 3)
  assert_eq(@examine.count_qualified_students(students, criteria2), 2)
}

ここでは、パッケージ名である @examine を付けて型と関数にアクセスしています。これは他の人があなたのパッケージを使うときの書き方ですが、ブラックボックステスト内では省略できます。

これで再びコンパイルが通り、テストも成功するはずです。

ライブラリを公開する#

準備ができたら、このプロジェクトを MoonBit のモジュールレジストリである mooncakes.io に公開できます。そこでは他にも興味深いプロジェクトを見つけられます。

  1. moon login を実行し、案内に従って既存の GitHub アカウントでアカウントを作成します。

  2. moon.mod.json のプロジェクト名を <your github account name>/<project name> に変更します。moon.pkg に他に影響する箇所がないか、moon check を実行して確認します。

  3. moon publish を実行すれば完了です。これで他の人がこのプロジェクトを利用できるようになります。

デフォルトでは、このプロジェクトは Apache 2.0 の下で公開されます。これは誰でも利用できる寛容なライセンスです。moon.mod.jsonlicense フィールドと LICENSE の内容を変更すれば、MulanPSL 2.0 のような他のライセンスも使えます。

まとめ#

ここまでで、MoonBit のごく基本的でありながら、決して単純ではない機能を学びました。しかし、MoonBit は機能豊富なマルチパラダイム言語です。文法や基本型についてさらに知りたい場合は 言語ツアー を参照し、そのほかのドキュメントも読んで MoonBit への理解を深めてください。