概要
(2025年6月1日現在, v0.120250416の) MoonBitのチュートリアルをやってみた記録です。
MoonBitとは
MoonBit is an end-to-end programming language toolchain for cloud and edge computing using WebAssembly.
「WebAssemblyを基盤としたプログラミング言語とツールチェイン」と思っておけば良さそう。
開発元は IDEA(International Digital Economy Academy, 粤港澳大湾区数字经济研究院)という中国の研究機関のようです。
language tour
まずはこのlanguage tourをやってみます。
なお、2025年6月1日現在、Qiitaにmoonbitのシンタクスハイライトがないため、以下rustのシンタクスハイライトで代用しています。
hello
まずは、helloです。
fn main {
println("hello")
}
- エントリーポイントはmain
- printlnで改行つきprint
- fnで関数定義
- {}でブロックを作成
- ;は必要ない
といった感じでしょうか。Cライク、というかRustライクな感じですね。
変数
続いて変数。
fn main {
let a : Int = 10
let b = 20
println(a + b)
let mut c = 10
c = c + 1
println(c)
let d = 20
// d = d + 1
println(d)
}
- letで変数定義
- :のあとに型指定
- 整数型はInt
- 型指定は省略可能で値から型推論される
- Int型同士で+演算が可能
- 変数はデフォルトでイミュータブル
- mutをつけるとミュータブルになる
- コメントは//
数値
数値です。
fn main {
let dec : Int = 1000000
let dec2 : Int = 1_000_000
let hex : Int = 0xFFFF
let oct = 0o777
let bin = 0b1001
println(1 + 2)
println(1 - 2)
println(1 * 2)
println(5 / 2)
println(10 % 3)
let num1 : Double = 3.14
let num2 : Float = 3.14
}
- 整数型Intは32ビット符号付き整数
- _を数値リテラルに挿入できる (そのほうが読みやすければ)
- プレフィックス0xで16進数、0oで8進数、0bで2進数の記述が可能
- +,*,-,/,%などの演算子を使用可能
- 浮動小数点型にはDouble型とFloat型がある
関数
fn add(a : Int, b : Int) -> Int {
return a + b
}
fn compute() -> Unit {
println(add(2, 40))
}
fn main {
compute()
}
- fnで関数定義
- ()内に引数を記述、->で戻り値の型を記述
- Unit型は戻り値がない場合 (Cのvoid相当)
- returnで戻り値を返却
- ()内に実引数を並べて呼び出し
ブロック
fn main {
let a = 100
{
let mut a = 0
println("checkpoint 1")
a = a + 1
}
println("checkpoint 2")
println(f())
}
fn f() -> Int {
let b = 3.14
let result = {
let b = 1
println("checkpoint 3")
b + 5
}
result // same as `return result` here
}
- 即時関数呼び出しのようなもの
- いくつか文を実行し、最後の式の値を返す (返さなくても良い)
- 名前空間的には別のスコープを形成して、外側の変数をshadowingする
- 関数の最後の式はreturnをつけなくても、戻り値として評価される
配列
fn main {
let arr1 : Array[Int] = [1, 2, 3, 4, 5]
let arr2 = Array::make(4,1)
println(arr1.length()) // get the length of the array
println(arr1[1]) // get the second element of the array
// We can also use the spread operator to concatenate arrays.
let arr3 = [..arr1, 1000, 2000, ..arr2, 3000, 4000]
println("spread arrays:")
println(arr3)
let view : ArrayView[Int] = arr1[1:4]
println("array view:")
println(view)
println("view[0]:")
println(view[0])
arr1.push(6) // push an element to the end
println("updated array:")
println(arr1)
}
- Arrayは可変長配列
- カンマ区切り[]でリテラルをかけるし、Array::make()で初期化しても良い
- Array::make()はサイズと初期化する値(全部がそれで埋められる)の2つの引数を取る
- length()メソッドあり
- インデキシングは[]を使用
- 既存の配列を展開するスプレッド構文あり
- [start:end]でビューを作成できる(コピーコスト削減)
- push()メソッドで要素を追加できる
- mutをつけないデフォルトのイミュータブルは、再代入禁止を意味していて、Arrayの場合要素はミュータブルになる
文字列
fn main {
let str = "Hello, World!"
// Access a character by index.
let c : Char = str[4]
println(c)
let c2 = 'o'
println(c == c2)
// Use escape sequence.
println("\nHello, \tWorld!")
println("unicode \u{1F407} is a rabbit")
// Concatenate two strings.
println(str + " Hello, MoonBit!")
// Use string interpolation.
let moon = "Moon"
let bit = "Bit"
println("Use \{moon + bit}. Happy coding")
}
- 文字列 String はUTF-16エンコードされた文字(Char)のシーケンス
- 文字列リテラルはダブルクォーテーションで囲む
- 文字はシングルクォーテーションで囲む
- 文字列に対して[]演算子を使用してインデックスアクセスが可能
- エスケープ文字としては改行\n, タブ\t, バックスラッシュ"\\, ダブルクォーテーション\", シングルクォーテーション\'など
- \u{1F407}など、ユニコードのコードポイントを記述するエスケープも使用可能
- +で文字列連結
- リテラル内は\{式}で文字列補間(string interpolation)ができる
タプル
fn main {
// create Tuple
let tuple = (3.14, false, [1,2,3])
let tuple2 : (Float, Bool, Int) = (2.1, true, 20)
println(tuple)
// Accessing tuple elements
println(tuple.0)
println(tuple.2)
// Tuple can also be destructured.
let (a, b, c) = f()
println("\{a}, \{b}, \{c}")
}
fn f() -> (Int, Bool, Double) {
(1, false, 3.14) // return multiple values via tuple
}
- タプルは異なる変数の組
- イミュータブル
- ()内,区切りで生成
- アクセスはドットアクセス(.0, .1など)で[]アクセスではない
- let (a, b) = tup で分解して変数に束縛することができる
- 関数から複数の値を返したいとき、タプルを使用すれば良い
マップ
fn main {
// Create a map by map literal
let map1 = { "key1": 1, "key2": 2, "key3": 3 }
println(map1)
// You can also create a map by Map::of, from a list of key-value pairs
let map2 = Map::of([("key1", 1), ("key2", 2), ("key3", 3)])
println(map1 == map2)
// Access a value by key
println(map1.get("key1"))
// Update a value by key
map1["key1"] = 10
println(map1)
// test a if a key exists
println(map1.contains("key1"))
}
- マップはいわゆる連想配列でキーバリューペアのコレクション
- ミュータブル
- キーもバリューも基本型(Int, String, Bool, Double, ...)であれば、マップリテラルで生成する
- そうでない場合はMap::of()で生成可能・これは引数にキーバリューペア(タプル)の配列を取る
- マップの値にアクセスするには[]を使用するか、get()メソッドを使用する
- マップの値を更新するにはm[key] = valueとすればよい
- マップにキーが含まれているかを判定するには、contains()メソッドを使用する
if
fn fib(x : Int) -> Int {
if x < 2 {
x
} else {
fib(x - 1) + fib(x - 2)
}
}
fn main {
if 5 > 1 {
println("5 is greater than 1")
}
println(fib(5))
println(weekday(3))
}
fn weekday(x : Int) -> String {
if x == 1 {
"Monday"
} else if x == 2 {
"Tuesday"
} else if x == 3 {
"Wednesday"
} else if x == 4 {
"Thursday"
} else if x == 5 {
"Friday"
} else if x == 6 {
"Saturday"
} else if x == 7 {
"Sunday"
} else {
"Invalid day"
}
}
- if, else if ..., elseで条件分岐を記述
- 条件部分に()は不要
- ifは式であり、if, elseで各ブロックの最後の式を評価して返す
- else節がなければUnit型とみなされる
ループ
fn main {
let array = [1, 2, 3]
println("for loop:")
for i = 0; i < array.length(); i = i + 1 {
println("array[\{i}]: \{array[i]}")
}
println("\nfunctional for loop:")
let sum = for i = 1, acc = 0; i <= 10; i = i + 1 {
let even = i % 2 == 0
continue i + 1, acc + i
} else {
acc
}
println(sum)
println("\nwhile loop:")
let mut j = 0
while true {
println("array[\{j}]: \{array[j]}")
j = j + 1
if j < array.length() {
continue
} else {
break
}
}
}
- for式は、Cスタイルのinit; condition; incrementで書く
- while式も、Cスタイルのwhile conditionで書く
- ループも式であり、最後にelse節で式を評価するか、または途中でループを脱出する場合はbreakのあとに続けて書かれた式を評価する
- for文の場合、continueのあとに書かれた式で、初期化で定義された変数を更新する
for-inループ
fn main {
println("for-in loop:")
let array = [1, 2, 3]
for element in array {
println("element: \{element}")
}
println("for-in loop with index:")
for i, element in array {
println("index: \{i}, element: \{element}")
}
println("for-in loop for map:")
let map = { "key1": 1, "key2": 2, "key3": 3 }
for k, v in map {
println("key: \{k}, value: \{v}")
}
}
- 配列やマップなどのコレクションを繰り返す場合は、for-inループを使用できる
- for-inループの対象になるコレクションとは、イテレータIter[V]を返すiter()や、イテレータIter2[V]を返すiter2()メソッドを実装している型の変数
range
fn main {
println("number 1 to 3:")
for i in 1..<4 {
println(i)
}
println("number 1 to 4:")
for i in 1..=4 {
println(i)
}
}
- rangeもfor-inループの対象にできる
- 右端open, closeは..<と..=で区別できる (Perl6のneko operatorを彷彿とさせるが、これは右端のみ)
エラー処理
///|
pub fn safe_add(i : Int, j : Int) -> Int! {
let signum_i = i & 0x80000000
let signum_j = j & 0x80000000
let result = i + j
if signum_i != signum_j {
result
} else {
let result_signum = result & 0x80000000
if result_signum != signum_i {
fail!("overflow")
} else {
result
}
}
}
///|
fn main {
let a = try {
safe_add!(1, 2)
} catch {
_ => panic()
}
try {
let result = safe_add!(@int.max_value, @int.max_value)
} catch {
Failure(error_message) => println(error_message)
_ => panic()
}
let result = safe_add?(@int.max_value, @int.max_value)
}
- 戻り価の型に!をつけると、エラーしうる関数という表現になる
- fail!()でエラーを発生させられる
- エラーはtry-catchで補足するか?を使用してResult型に変換する
- catch節ではパターンマッチを使える
- panic()はプログラムを終了させる
テスト
test {
assert_eq!(1, 1 + 2)
assert_false!(1 == 2)
assert_true!([1,2,3] == [1,2,3])
}
test {
inspect!(fib(5))
inspect!([1,2,3,4].map(fib))
}
// Add test name to make it more descriptive.
test "fibonacci" {
inspect!(fib(5), content="5")
inspect!(fib(6), content="8")
}
fn fib(n : Int) -> Int {
if n < 2 {
n
} else {
fib(n - 1) + fib(n - 2)
}
}
- テストのための構文が組み込まれている
- inspect!はスナップショットテストを行うための構文で、CLIで
moon test --update
とするとスナップショットを取る
構造体
struct Point {
x : Int
y : Int
} derive(Show)
fn main {
// create a point
let point = { x: 3, y: 4 }
println("point: \{point}")
println("point.x: \{point.x}")
println("point.y: \{point.y}")
// functional update
let point2 = {..point, x: 20}
println(point2)
}
- 構造体定義はstructで行う
- derive(show)はprintできるようにするためのもの
- フィールド名から推論するので、必ずしも型名を記述してインスタンスを作る必要はない
- 必要なら
Point::{ x: 3, y: 4 }
などとする - フィールドはdefaultでイミュータブル
- functional updateという仕組み(スプレッド構文に似ている)で、部分更新した新しいインスタンスを作れる
構造体のミュータブルなフィールド
struct MutPoint {
mut mx : Int
y : Int
} derive(Show)
fn main {
let point = { mx: 3, y: 4 }
println("point: \{point}")
point.mx = 10
println("point: \{point}")
}
- 構造体のフィールドをミュータブルにすれば、フィールドの宣言時にmutとつければ良い
列挙型
enum Color {
Red
Green
Blue
RGB(Int, Int, Int)
CMYK(Int, Int, Int, Int)
} derive(Show)
fn print_color(color : Color) -> Unit {
match color {
Red => println("Red")
Green => println("Green")
Blue => println("Blue")
// Take the three Int values from RGB and print them.
RGB(r, g, b) => println("RGB: \{r}, \{g}, \{b}")
CMYK(c, m, y, k) => println("CMYK: \{c}, \{m}, \{y}, \{k}")
}
}
fn main {
let red = Red
let green = Color::Green
let blue = RGB(0, 0, 255)
let black = CMYK(0, 0, 0, 100)
print_color(red)
print_color(green)
print_color(blue)
print_color(black)
}
- enumは、ふつうの列挙子の他に、データを関連付けた列挙子を持つことができる
- パターンマッチmatchで関連付けられたデータを変数に束縛することができる
ニュータイプ
type UserId Int derive(Show)
type UserName String derive(Show)
fn main {
let user_id : UserId = UserId(1)
let user_name : UserName = UserName("Alice")
println(user_id._)
println(user_name._)
// use some pattern matching to extract the values
let UserId(id) = user_id
let UserName(name) = user_name
}
-
type newType oldType
で新しい型を作る - 型エイリアスではなく、一つのコンストラクタを持つ列挙型と考える (元の型をwrapした新しい型)
- 中身にアクセスするには
._
を使用する - パターンマッチを使用した変数束縛も可能
オプション
fn first_char(s : String) -> Option[Char] {
if s.length() == 0 {
None
} else {
Some(s[0])
}
}
fn main {
let c1 : Char? = first_char("hello")
let c2 : Option[Char] = first_char("")
println("\{c1.is_empty()}, \{c1.unwrap()}")
println("\{c2.is_empty()}, \{c2}")
}
- Option[T]型の変数はNoneかT型の変数をラップしたSomeのいずれかの値を持つ
- T?はOption[T]の糖衣構文
- is_empty()メソッドでNoneかどうかを判定、unwrap()メソッドでSomeの中身を取り出す
- unwrap()をNoneに対して呼び出すとpanicする
リザルト
fn first_char(s : String) -> Result[Char, String] {
if s.length() == 0 {
Err("empty string")
} else {
Ok(s[0])
}
}
fn main {
let c1 = first_char("hello")
let c2 = first_char("")
println("\{c1.is_ok()}, \{c1}, \{c1.unwrap()}")
println("\{c2.is_err()}, \{c2}")
}
- Optionだと異常系がNoneなので詳細を呼び出し元に返せない
- そこでResultではエラーメッセージを含められるようになっている
- Result[T1, T2]はT2をラップしたErrか、T1をラップしたOkのいずれかの値を持つ
- Okかを判定するis_ok()メソッドや、Okの中身を取り出すunwrap()メソッドがある
- 正しくエラーハンドリングするにはパターンマッチの使用が推奨される
パターンマッチ
struct Point {
x : Int
y : Int
} derive(Show)
fn main {
let tuple = (1, false, 3.14)
let (a, b, c) = tuple
println("a:\{a}, b:\{b}, c:\{c}")
let record = { x: 5, y: 6 }
let { x, y } = record
println("x:\{x}, y:\{y}")
}
- すでに見たようにタプルや構造体を、(他の言語では)構造化束縛などと呼ばれる方法で、変数に束縛することができる
let文におけるパターンマッチ
fn main {
f([1, 2, 3])
f([1, 2])
}
fn f(array : Array[Int]) -> Unit {
let [a, b, c] = array // warning as error
println("a:\{a}, b:\{b}, c:\{c}")
}
- let文でパターンマッチできるのはすでに見た通り
- 配列をパターンマッチさせようとすると、実はこのコードは動かない
- というのは、デフォルトでpartial match warningがエラー扱いになるから
- partial match warningは、完全に配列のサイズが同じでない場合にしか動かないよ、という警告
match式におけるパターンマッチ
enum Resource {
TextFile(String)
Image(String)
Folder(Map[String, Resource])
} derive(Show)
let assets : Resource = Folder(
{
"readme.md": TextFile("hello world"),
"image.jpg": Image("https://someurl1"),
"folder1": Folder(
{
"src1.mbt": TextFile("some code1"),
"src2.mbt": TextFile("some MoonBit code 2"),
},
),
"folder2": Folder(
{
"src3.mbt": TextFile("some code3"),
"image2.jpg": Image("https://someurl2"),
},
),
},
)
fn main {
println("resource count: \{count(assets)}")
}
fn count(res : Resource) -> Int {
match res {
Folder(map) => {
let mut sum = 0
for name, res in map {
sum += count(res)
}
sum
}
TextFile(_) => 1
Image(_) => 1
}
}
- match式を用いて処理を分岐することができる
- match式は上から順に見ていき、マッチした分岐に入る
- いずれにもマッチしなかった場合、プログラムが停止する
- Image(_) => 1 のようにラップした中身の価に興味がなければ、_で無視することができる
- サラッと流されているが、enumの定義は再帰できる
定数パターン
fn fibonacci(x : Int) -> Int {
// assume x > 0
match x {
1 => 1
2 => 2
_ => fibonacci(x - 1) + fibonacci(x - 2)
}
}
fn negate(x : Bool) -> Bool {
match x {
true => false
false => true
}
}
fn read(x : Char) -> Int? {
match x {
'1' => Some(1)
'2' => Some(2)
'3' => Some(3)
_ => None
}
}
fn contents(file : String) -> String? {
match file {
"README" => Some("# hello world")
"hello.mbt" => Some("println(\"hello world\")")
_ => None
}
}
fn main {
println("fib(5): \{fibonacci(5)}")
println("negate(false): \{negate(false)}")
println("read('2'): \{read('2')}, read('5'): \{read('5')}")
println(contents("README"))
}
- 定数パターンは、マッチ式内で定数にマッチさせるパターン
タプルパターン
fn logical_and(x : Bool, y : Bool) -> Bool {
match (x, y) {
(true, true) => true
(false, _) => false
(_, false) => false
}
}
fn logical_or(x : Bool, y : Bool) -> Bool {
match (x, y) {
(true, _) => true
(_, true) => true
_ => false
}
}
fn main {
println("true and false: \{logical_and(true, false)}")
println("true or false: \{logical_or(true, false)}")
}
- 複数の条件をマッチさせるには、タプルパターンを使用する
- タプルを生成するコストは発生しない (コンパイラが最適化する)
エイリアスパターン
fn main {
let (a, (b, _) as tuple, _) as triple = (1, (true, 5), false)
println("a: \{a}, b: \{b}")
println("tuple: \{tuple}")
println("triple: \{triple}")
}
- 変数に束縛するとき、複数の階層で同時に束縛することができる
-
pattern as name
というのがその構文で、エイリアスパターンという
配列パターン
fn main {
let array = [1, 2, 3, 4, 5, 6]
match array {
[a, b, ..] => println("a: \{a}, b: \{b}")
_ => ()
}
match array {
[.., c, d] => println("c: \{c}, d: \{d}")
_ => ()
}
match array {
[e, .., f] => println("e: \{e}, f: \{f}")
_ => ()
}
println("sum of array: \{sum(array)}")
}
fn sum(array : @array.View[Int]) -> Int {
match array {
[] => 0
[x, .. xs] => x + sum(xs)
}
}
- 配列のパターンマッチは、[]を用いて行う
- ..で「のこり」の部分を指定する
- ..の「のこり」の部分にはエイリアスをつけることができる
Orパターン
enum Color {
Blue
Red
Green
RGB(Int, Int, Int)
RGBA(Int, Int, Int, Int)
} derive(Show)
fn get_green(color : Color) -> Int {
match color {
Blue | Red => 0
Green => 255
RGB(_, g, _) | RGBA(_, g, _, _) => g
}
}
fn main {
println("The green part of Red is \{get_green(Red)}")
println("The green part of Green is \{get_green(Green)}")
println("The green part of Blue is \{get_green(Blue)}")
println("The green part of RGB(0,0,0) is \{get_green(RGB(0,0,0))}")
println("The green part of RGBA(50,5,0,6) is \{get_green(RGBA(50,5,0,6))}")
}
- |で区切ることで、「または」の条件でパターンマッチさせることができる
- 同じ型で同じ名前に限り、変数束縛も可能
Rangeパターン
fn score_to_grade(score : Int) -> String {
match score {
0..<60 => "F"
60..<70 => "D"
70..<80 => "C"
80..<90 => "B"
90..=100 => "A"
_ => "Invalid score"
}
}
fn classify_char(c : Char) -> String {
match c {
'A'..='Z' => "UpperCase"
'a'..='z' => "LowerCase"
'0'..='9' => "Digit"
_ => "Special"
}
}
fn is_scalar_value(codepoint : Int) -> Bool {
match codepoint {
0x0000..=0xD7FF | 0xE000..=0x10FFFF => true
_ => false
}
}
fn main {
println(score_to_grade(50))
println(score_to_grade(85))
println(score_to_grade(95))
println(classify_char('A'))
println(classify_char('1'))
println(classify_char('!'))
println(is_scalar_value(0xD500))
}
- 整数のシーケンスにマッチさせる場合には、Rangeパターンが使える
- Byte, Int, UInt, Int64, UInt64, Charなどの整数型に限る
イミュータブルリスト
fn count[A](list : @immut/list.T[A]) -> UInt {
match list {
Nil => 0
Cons(_, rest) => count(rest) + 1 // may overflow
}
}
///|
fn main {
let empty_list : @immut/list.T[Int] = Nil
let list_1 = @immut/list.Cons(1, empty_list)
let list_2 = @immut/list.Cons(2, list_1)
let list_3 = @immut/list.Cons(3, empty_list)
let reversed_1 = list_1.rev()
let n = count(reversed_1)
}
-
@immut/list
パッケージの型 - NilまたはCons(最初の要素と残り)のいずれかの値を持つ
- サラッとジェネリクスが例で使われている。
fn func[T]
でジェネリックな関数定義となる
イミュータブルセット
///|
fn main {
let a = @immut/hashset.of([5, 4, 3, 2, 1])
let b = @immut/sorted_set.of([5, 4, 3, 2, 1])
let arraya = a.iter().collect()
let arrayb = b.iter().collect()
let c = a.add(6)
let d = b.add(6)
let except_one = d.remove_min()
println(d)
}
-
@immut/hashset
または@immut/sorted_set
の型 - hashsetはハッシュを使ったセットなので、順序は関係ない
- sorted_setは木構造を使ったセットで、整列されている
- C++ならunordered_setとsetに相当
イミュータブルマップ
///|
fn main {
let a = @immut/hashmap.of([(1, "a"), (2, "b"), (3, "c")])
let b = a.add(4, "d")
let c = @immut/sorted_map.from_iter(b.iter())
println(c.keys())
}
- セットと同様
メソッド
type MyInt Int
fn increment(self : MyInt) -> MyInt {
MyInt(self._ + 1)
}
fn MyInt::println(x : MyInt) -> Unit {
println("MyInt(\{x._})")
}
fn main {
let x = MyInt(39)
let y = x.increment() // call method via dot syntax
let z = increment(y) // `fn increment(..)` can be called directly
let w = MyInt::increment(z) // call methods using qualified syntax
w.println()
MyInt::println(w) // `fn MyInt::println` cannot be called via `println(..)` directly
}
- すでにメソッドは出てきたが、メソッドの定義は2通りある
- 一つはトップレベル関数として定義し、オブジェクトを第一引数selfという名前で受け取る方法
- もう一つは
fn T::method(x: T)
と限定子付きの関数として定義し、オブジェクトを第一引数で受け取る方法(この場合名前は何でも良い) - 最初の方法で定義した場合は、直接呼び出しが可能になる
- だが、トップレベル関数で名前の衝突は許可されていないので、たとえば、T1::method()とT2::method()を両方定義したいのであれば、2つめの方法で限定子付きの関数として定義するしかない
感想
とりあえず、現時点で言語のチュートリアルはここまで。
チュートリアルは言語機能をカバーできておらず、特にパッケージや参照など気になるトピックは残っているものの、ここまでの感想としては、概ねC系の言語をやってきた人ならとっつきやすいのではないかと思いました。
パターンマッチなどの機能は最近の言語にはよく見られるもので、クセの少ない言語だと思います。
Tour for Beginners
次にこちらのTour for Beginnersをやります。
インストール
VSCodeの拡張機能からインストールすることが推奨されています。
こちらから、拡張機能をインストールして、WSLのワークスペースを開いたうえで、Ctrl-Shift-Pでコマンドパレットを開き、"Moonbit: Install moonbit toolchain"を指定すると、インストールスクリプトをダウンロードして実行し始めます。
* 実行するタスク: curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash -s '0.1.20250529+8a98c8e02'
Downloading moonbit ...
######################################################################## 100.0%
Downloading core ...
######################################################################## 100.0%
Bundling core ...
Warning: [0029]
╭─[/home/hokazono/.moon/lib/core/bigint/moon.pkg.json:4:5]
│
4 │ "moonbitlang/core/char",
│ ───────────┬───────────
│ ╰───────────── Warning: Unused package 'moonbitlang/core/char'
───╯
[======================================- ] 55/57 done, 1/1 running
Finished. moon: ran 57 tasks, now up to date
[=======================================-] 56/57 done, 1/1 running
Finished. moon: ran 57 tasks, now up to date
moonbit was installed successfully to ~/.moon
To verify the downloaded binaries, check https://www.moonbitlang.com/download#verifying-binaries for instructions.
To know how to add shell completions, run 'moon shell-completion --help'
Added "~/.moon/bin" to $PATH in "~/.bashrc"
To get started, run:
source ~/.bashrc
moon help
* 任意のキーを押してターミナルを終了します。
インストールが終わったようです。${HOME}/.moon
に関連するファイルが配置されるようです。
$ tree ~/.moon -L 2
/home/hokazono/.moon
├── bin
│ ├── internal
│ ├── lsp-server.js
│ ├── moon
│ ├── moonc
│ ├── mooncake
│ ├── moon_cove_report
│ ├── moondoc
│ ├── moonfmt
│ ├── mooninfo
│ ├── moonlex.wasm
│ ├── moonrun
│ └── moonyacc.wasm
├── include
│ ├── moonbit-fundamental.h
│ └── moonbit.h
└── lib
├── core
├── libmoonbitrun.o
├── libtcc1.a
├── runmain.o
├── runtime.c
└── runtime_core.c
また、bashrc
には$HOME/.moon/bin
をPATHに追加する文が追加されています。
$ tail -2 ~/.bashrc
# moonbit
export PATH="$HOME/.moon/bin:$PATH"
さっそく、moon
コマンドを叩いてみます。
$ moon
The build system and package manager for MoonBit.
Usage: moon [OPTIONS] <COMMAND>
Commands:
new Create a new MoonBit module
build Build the current package
check Check the current package, but don't build object files
run Run a main package
test Test the current package
clean Remove the target directory
fmt Format source code
doc Generate documentation
info Generate public interface (`.mbti`) files for all packages in the module
bench Run benchmarks in the current package
add Add a dependency
remove Remove a dependency
install Install dependencies
tree Display the dependency tree
login Log in to your account
register Register an account at mooncakes.io
publish Publish the current module
package Package the current module
update Update the package registry index
coverage Code coverage utilities
generate-build-matrix Generate build matrix for benchmarking (legacy feature)
upgrade Upgrade toolchains
shell-completion Generate shell completion for bash/elvish/fish/pwsh/zsh to stdout
version Print version information and exit
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
Common Options:
-C, --directory <SOURCE_DIR> The source code directory. Defaults to the current directory
--target-dir <TARGET_DIR> The target directory. Defaults to `source_dir/target`
-q, --quiet Suppress output
-v, --verbose Increase verbosity
--trace Trace the execution of the program
--dry-run Do not actually run the command
--build-graph Generate build graph
ちゃんとインストールされているようですね。
さっそく、moon new
コマンドを使用して、プロジェクトを作成してみます。
$ moon new
Enter the path to create the project (. for current directory): .
Select the create mode: exec
Enter your username: hokacci
Enter your project name: sputnik
Enter your license: No License
Initialized empty Git repository in /home/hokazono/repos/sputnik/.git/
Created .
こんな感じで作られました。
Exampleプロジェクト
複数の生徒のスコアが入力され、合格した生徒の数を出力する関数を実装します。
次のようなコードを追加してみました。
src/lib/top.mbt
// 生徒
pub(all) struct Student {
id : String
score : Double
} derive(Show)
// 試験結果 (合格/不合格)
pub(all) enum ExamResult {
Pass
Fail
} derive(Eq, Show)
// 合格基準を満たすかどうかを判定する関数
pub fn is_qualified(student : Student, criteria: Double) -> ExamResult {
if student.score >= criteria {
Pass
} else {
Fail
}
}
// 合格者の数を数える関数
pub fn count_qualified_students(
students : Array[Student],
is_qualified : (Student) -> ExamResult
) -> Int {
let mut count = 0
for student in students {
count += match is_qualified(student) {
Pass => 1
Fail => 0
}
}
count
}
src/lib/top_test.mbt
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)
}
test "count qualified students" {
let students: Array[Student] = [
{ 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)
}
テスト
テストを実行すると、もともとあったテストに加えて成功しているようです。
やった!
$ moon test
Total tests: 3, passed: 3, failed: 0
感想
公式のチュートリアルに沿って、言語機能を軽く見て、インストールしてExampleプロジェクトを動かしました。
今回のチュートリアルでは、WebAssemblyがベースになっている利点を感じることはできなかったので、また今度ブラウザから今回作ったモジュールを呼び出すとかしてみたいと思いました。