基礎#
組み込みデータ構造#
Unit#
MoonBit の組み込み型 Unit は、意味のある値が存在しないことを表します。値は 1 つだけで、() と書きます。Unit は C/C++/Java などの言語における void に似ていますが、void とは異なり実際の型であり、型が期待される場所ならどこでも使えます。
Unit 型は、何らかの処理を行うものの意味のある結果を返さない関数の戻り値型としてよく使われます:
fn print_hello() -> Unit {
println("Hello, world!")
}
MoonBit では Unit は第一級の型として扱われるため、ジェネリクスで使ったり、データ構造に格納したり、関数の引数として渡したりできます。
Boolean#
MoonBit には組み込みの真偽値型があり、値は true と false の 2 つです。真偽値型は条件式や制御構造で使われます。! を使うと真偽値を反転できます。not(x) も同じ意味です。
let a = true
let b = false
let c = a && b
let d = a || b
let e = !a
let f = !(a && b)
数値#
MoonBit には整数型と浮動小数点数型があります:
型 |
説明 |
例 |
|---|---|---|
|
16 ビット符号付き整数 |
|
|
32 ビット符号付き整数 |
|
|
64 ビット符号付き整数 |
|
|
16 ビット符号なし整数 |
|
|
32 ビット符号なし整数 |
|
|
64 ビット符号なし整数 |
|
|
IEEE 754 によって定義された 64 ビット浮動小数点数 |
|
|
32 ビット浮動小数点数 |
|
|
他の型より大きな数値を表します |
|
MoonBit は、10 進数、2 進数、8 進数、16 進数を含む数値リテラルもサポートしています。
可読性を高めるために、1_000_000 のように数値リテラルの途中にアンダースコアを入れられます。アンダースコアは 3 桁ごとだけでなく、数値の任意の位置に置けます。
10 進数では、数字の間にアンダースコアを入れられます。
デフォルトでは、整数リテラルは符号付き 32 ビット数として扱われます。符号なし数値には接尾辞
Uが、64 ビット数値には接尾辞Lが必要です。let a = 1234 let b : Int = 1_000_000 + a let unsigned_num : UInt = 4_294_967_295U let large_num : Int64 = 9_223_372_036_854_775_807L let unsigned_large_num : UInt64 = 18_446_744_073_709_551_615UL
2 進数は先頭に 0 があり、その後に文字
Bが続く形式、つまり0b/0Bです。0b/0Bの後の桁は0または1でなければなりません。let bin = 0b110010 let another_bin = 0B110010
8 進数は先頭に 0 があり、その後に文字
Oが続く形式、つまり0o/0Oです。0o/0Oの後の桁は0から7の範囲でなければなりません:let octal = 0o1234 let another_octal = 0O1234
16 進数は先頭に 0 があり、その後に文字
Xが続く形式、つまり0x/0Xです。0x/0Xの後の桁は0123456789ABCDEFの範囲でなければなりません。let hex = 0XA let another_hex = 0xA_B_C
浮動小数点数リテラルは 64 ビット浮動小数点数です。
Floatを定義するには型注釈が必要です。let double = 3.14 // Double let float : Float = 3.14 let float2 = (3.14 : Float)
64 ビット浮動小数点数は 16 進数形式でも定義できます:
let hex_double = 0x1.2P3 // (1.0 + 2 / 16) * 2^(+3) == 9
期待される型が分かっている場合、MoonBit はリテラルを自動的にオーバーロードできるため、文字接尾辞で数値型を指定する必要はありません:
let int : Int = 42
let uint : UInt = 42
let int64 : Int64 = 42
let double : Double = 42
let float : Float = 42
let bigint : BigInt = 42
String#
String は UTF-16 コードユニットの列を保持します。ダブルクォートで文字列を作成することも、#| を使って複数行文字列を書くこともできます。
let a = "兔rabbit"
debug_inspect(a.code_unit_at(0).to_char(), content="Some('兔')")
debug_inspect(a.code_unit_at(1).to_char(), content="Some('r')")
let b =
#| Hello
#| MoonBit\n
#|
println(b)
Hello
MoonBit\n
ダブルクォートの文字列では、バックスラッシュの後に特定の特殊文字を続けるとエスケープシーケンスになります:
エスケープシーケンス |
説明 |
|---|---|
|
改行、復帰、水平タブ、バックスペース |
|
バックスラッシュ |
|
Unicode エスケープシーケンス |
MoonBit は文字列補間をサポートしています。補間文字列の中に変数を埋め込めます。この機能により、変数の値をテキストに直接埋め込んで動的な文字列を作りやすくなります。文字列補間に使う変数は Show トレイト を実装している必要があります。
let x = 42
println("The answer is \{x}")
注釈
補間式には改行、{}、" を含められません。
複数行文字列は先頭に #| または $| を付けて定義できます。前者は生文字列をそのまま保持し、後者はエスケープと補間を行います:
let lang = "MoonBit"
let raw =
#| Hello
#| ---
#| \{lang}
#| ---
let interp =
$| Hello
$| ---
$| \{lang}
$| ---
println(raw)
println(interp)
Hello
---
\{lang}
---
Hello
---
MoonBit
---
同じ複数行文字列の中で $| と #| を混在させないでください。ブロック全体でどちらか一方のスタイルに統一してください。
VSCode 拡張機能 には、貼り付けた文書を通常の複数行文字列に変換したり、通常テキストと MoonBit の複数行文字列を切り替えたりできるアクションがあります。
期待される型が String の場合、配列リテラル構文は文字列中の各文字を指定して String を構築するようにオーバーロードされます。
test {
let c : Char = '中'
let s : String = [c, '文']
inspect(s, content="中文")
}
Char#
Char は Unicode のコードポイントを表します。
let a : Char = 'A'
let b = '兔'
let zero = '\u{30}'
let zero = '\u0030'
文字リテラルは、期待される型が Int または UInt16 の場合、その型にオーバーロードできます:
test {
let s : String = "hello"
let b : UInt16 = s.code_unit_at(0) // 'h'
assert_eq(b, 'h') // 'h' is overloaded to UInt16
let c : Int = '兔'
// Not ok : exceed range
// let d : UInt16 = '𠮷'
}
Byte(s)#
MoonBit のバイトリテラルは、1 つの ASCII 文字または 1 つのエスケープからなり、b'...' の形式を取ります。バイトリテラルの型は Byte です。例えば:
fn main {
let b1 : Byte = b'a'
println(b1.to_int())
let b2 = b'\xff'
println(b2.to_int())
}
97
255
Bytes は不変なバイト列です。バイトと同様に、bytes リテラルは b"..." の形式を取ります。例えば:
test {
let b1 : Bytes = b"abcd"
let b2 = b"\x61\x62\x63\x64"
assert_eq(b1, b2)
}
バイトリテラルと bytes リテラルもエスケープシーケンスをサポートしますが、文字列リテラルのものとは異なります。次の表は、byte / bytes リテラルでサポートされるエスケープシーケンスです:
エスケープシーケンス |
説明 |
|---|---|
|
改行、復帰、水平タブ、バックスペース |
|
バックスラッシュ |
|
16 進数エスケープシーケンス |
|
8 進数エスケープシーケンス |
注釈
@buffer.T を使うと、さまざまな種類のデータを書き込んで bytes を構築できます。例えば:
test "buffer 1" {
let buf : @buffer.Buffer = Buffer()
buf.write_bytes(b"Hello")
buf.write_byte(b'!')
assert_eq(buf.contents(), b"Hello!")
}
配列リテラルも、並びの各 byte を指定することで Bytes 列を構築するようにオーバーロードできます。
test {
let b : Byte = b'\xFF'
let bs : Bytes = [b, b'\x01']
inspect(
bs,
content=(
#|b"\xff\x01"
),
)
}
参考
Byte の API: https://mooncakes.io/docs/moonbitlang/core/byte Bytes の API: https://mooncakes.io/docs/moonbitlang/core/bytes @buffer.T の API: https://mooncakes.io/docs/moonbitlang/core/buffer
バイトコンテナの選び方#
MoonBit にはバイト指向のコンテナ型がいくつかあります。互いに関連していますが、役割は異なります:
Type |
所有権 / 可変性 |
サイズ変更可 |
主な用途 |
|---|---|---|---|
|
所有権あり,不変 |
いいえ |
最終的な byte ペイロード、API 境界、シリアライズ済みデータ |
|
借用された不変ビュー |
いいえ |
既存の bytes をコピーせずにスライスまたはパースする |
|
所有権あり,可変 |
はい |
汎用の可変 byte ストレージ |
|
所有権あり,可変 |
いいえ |
固定サイズの作業バッファ |
|
借用された配列ビュー |
いいえ |
所有権を持たずに配列ベースの byte ストレージのスライスを渡す |
|
借用された可変ビュー |
いいえ |
借用した配列ベースの byte ストレージをその場で変更する |
|
所有権あり,可変ビルダー |
はい |
bytes を段階的に構築し、最後に |
特に重要なのは次の 2 つの違いです:
BytesとBytesViewの違い:所有された不変データと、借用された不変スライス。Array[Byte]とArrayView[Byte]/MutArrayView[Byte]の違い:所有された可変ストレージと、それに対する借用された読み取り専用または可変ビュー。
ReadOnlyArray[Byte] と MutArrayView[Byte] は、それぞれの制約を明示したい場合に使う対応する読み取り専用/可変ビュー型です。パターンマッチとビット文字列パースもこれらのバイトコンテナで使えます。詳細は Array Pattern と Bitstring Pattern を参照してください。
タプル#
タプルは、カンマ , で要素を区切り、丸括弧 () で構成される有限個の値の集合です。要素の順序は重要です。例えば (1,true) と (true,1) は異なる型になります。例を示します:
fn main {
fn pack(
a : Bool,
b : Int,
c : String,
d : Double
) -> (Bool, Int, String, Double) {
(a, b, c, d)
}
let quad = pack(false, 100, "text", 3.14)
let (bool_val, int_val, str, float_val) = quad
println("\{bool_val} \{int_val} \{str} \{float_val}")
}
false 100 text 3.14
タプルはパターンマッチまたは添字でアクセスできます:
test {
let t = (1, 2)
let (x1, y1) = t
let x2 = t.0
let y2 = t.1
assert_eq(x1, x2)
assert_eq(y1, y2)
}
Ref#
Ref[T] は、型 T の値 val を保持する可変参照です。
{ val : x } で構築でき、ref.val でアクセスできます。詳細は struct を参照してください。
let a : Ref[Int] = { val: 100 }
test {
a.val = 200
assert_eq(a.val, 200)
a.val += 1
assert_eq(a.val, 201)
}
Option and Result#
MoonBit で起こりうるエラーや失敗を表す最も一般的な型が Option と Result です。
Option[T]は、型Tの値が存在しない可能性を表します。T?と略記できます。Result[T, E]は、型Tの値か型Eのエラーのいずれかを表します。
詳細は enum を参照してください。
test {
let a : Int? = None
let b : Option[Int] = Some(42)
let c : Result[Int, String] = Ok(42)
let d : Result[Int, String] = Err("error")
match a {
Some(_) => assert_true(false)
None => assert_true(true)
}
match d {
Ok(_) => assert_true(false)
Err(_) => assert_true(true)
}
}
参考
Option の API: https://mooncakes.io/docs/moonbitlang/core/option Result の API: https://mooncakes.io/docs/moonbitlang/core/result
配列#
配列は、角括弧 [] で構成され、要素をカンマ , で区切る有限個の値の列です。例えば:
let numbers = [1, 2, 3, 4]
numbers[x] を使って x 番目の要素を参照できます。インデックスは 0 から始まります。
test {
let numbers = [1, 2, 3, 4]
let a = numbers[2]
numbers[3] = 5
let b = a + numbers[3]
assert_eq(b, 8)
}
Array[T] と FixedArray[T] があります。ビューは ArrayView[T] と MutArrayView[T] で提供されます(下記参照)。
Array[T] はサイズを伸ばせますが、FixedArray[T] は固定サイズなので、初期値を与えて作成する必要があります。
警告
FixedArray を同じ初期値で作成するのはよくある落とし穴です:
test {
let two_dimension_array = FixedArray::make(10, FixedArray::make(10, 0))
two_dimension_array[0][5] = 10
assert_eq(two_dimension_array[5][5], 10)
}
これは、すべてのセルが同じオブジェクト(この場合は FixedArray[Int])を参照しているためです。代わりに FixedArray::makei() を使うと、各インデックスごとに個別のオブジェクトが作られます。
test {
let two_dimension_array = FixedArray::makei(10, fn(_i) {
FixedArray::make(10, 0)
})
two_dimension_array[0][5] = 10
assert_eq(two_dimension_array[5][5], 0)
}
期待される型が分かっている場合、MoonBit は配列リテラルを自動的にオーバーロードできます。そうでない場合は Array[T] が生成されます:
let fixed_array_1 : FixedArray[Int] = [1, 2, 3]
let fixed_array_2 = ([1, 2, 3] : FixedArray[Int])
let array_3 : Array[Int] = [1, 2, 3] // Array[Int]
ArrayView#
他の言語の slice と同様に、ビューはコレクションの特定区間への参照です。data[start:end] を使うと、配列 data の start から end(終端は含まない)までを参照するビューを作れます。start と end の両方は省略できます。
注釈
ArrayView 自体は不変データ構造ですが、元の Array や FixedArray は変更されうる場合があります。可変ビューが必要なら、data.mut_view(...) で MutArrayView[T] を使ってください。
test {
let xs = [0, 1, 2, 3, 4, 5]
let s1 : ArrayView[Int] = xs[2:]
@test.assert_eq(s1.to_owned(), [2, 3, 4, 5])
@test.assert_eq(xs[:4].to_owned(), [0, 1, 2, 3])
@test.assert_eq(xs[2:5].to_owned(), [2, 3, 4])
@test.assert_eq(xs[:].to_owned(), [0, 1, 2, 3, 4, 5])
let mv : MutArrayView[Int] = xs.mut_view(start=1, end=3)
mv[0] = 99
inspect(xs[1], content="99")
}
Map#
MoonBit の標準ライブラリには、挿入順を保持する Map というハッシュマップ型があります。Map は便利なリテラル構文で作成できます:
let map : Map[String, Int] = { "x": 1, "y": 2, "z": 3 }
現在、Map リテラル構文のキーは定数である必要があります。Map はパターンマッチでもきれいに分解でき、Map Pattern を参照してください。
Json#
MoonBit はリテラルのオーバーロードによって、便利な JSON 処理をサポートします。式の期待型が Json のとき、数値・文字列・配列・Map リテラルをそのまま JSON データとして使えます:
let moon_pkg_json_example : Json = {
"import": ["moonbitlang/core/builtin", "moonbitlang/core/coverage"],
"test-import": ["moonbitlang/core/random"],
}
Json 値もパターンマッチできます。Json Pattern を参照してください。
オーバーロードされたリテラル#
オーバーロードされたリテラルを使うと、同じ構文で異なる型の値を表現できます。例えば 1 は、期待型に応じて UInt や Double を表せます。期待型が分からない場合、リテラルは既定で Int として解釈されます。
fn expect_double(x : Double) -> Unit {
}
test {
let x = 1 // type of x is Int
let y : Double = 1
expect_double(1)
}
オーバーロードされたリテラルは組み合わせられます。配列リテラルを Bytes に、数値リテラルを Byte にオーバーロードできるなら、[1,2,3] を Bytes としても解釈できます。MoonBit におけるオーバーロードされたリテラルの表は次のとおりです:
オーバーロードされたリテラル |
既定の型 |
オーバーロード先 |
|---|---|---|
|
|
|
|
|
— |
|
|
|
|
|
|
|
|
|
パターンにも同様のオーバーロード規則があります。詳しくは Pattern Matching を参照してください。
注釈
リテラルのオーバーロードは値変換とは異なります。変数を別の型へ変換するには、to_int() や to_double() など、to_ で始まるメソッドを使ってください。
オーバーロードされたリテラル内のエスケープシーケンス#
エスケープシーケンスは、オーバーロードされた "..." リテラルと '...' リテラルで使えます。エスケープシーケンスの解釈は、オーバーロード先の型によって異なります:
基本エスケープシーケンス
\n、\r、\t、\\、\bです。これらは任意の"..."または'...'リテラルで使えます。StringやBytesでは、それぞれ対応するCharまたはByteとして解釈されます。バイトエスケープシーケンス
\x41と\o102のエスケープシーケンスはByteを表します。これらはBytesとByteにオーバーロードされるリテラルで使えます。Unicode エスケープシーケンス
\u5154と\u{1F600}のエスケープシーケンスはCharを表します。これらはStringとChar型のリテラルで使えます。
関数#
関数は引数を受け取り、結果を返します。MoonBit では関数は第一級値なので、他の関数の引数や戻り値にできます。MoonBit の命名規則では、関数名を大文字 A-Z で始めてはいけません。下の enum 節でコンストラクタと比較してください。
トップレベル関数#
関数はトップレベルでもローカルでも定義できます。たとえば、fn キーワードを使って 3 つの整数を合計し、その結果を返すトップレベル関数は次のように定義できます:
fn add3(x : Int, y : Int, z : Int) -> Int {
x + y + z
}
トップレベル関数の引数と戻り値には、明示的な型注釈が必要です。
トップレベル関数とメソッドは declare で導入することもできます。宣言された関数はシグネチャだけを持ち、本体は持ちません。後から置く実装はそのシグネチャと一致している必要があります。実装を置く前に API の形を先に利用可能にしたい場合に便利です。
declare fn declared_add(x : Int, y : Int) -> Int
fn declared_add(x : Int, y : Int) -> Int {
x + y
}
struct DeclaredCounter(Int)
declare fn DeclaredCounter::value(self : Self) -> Int
fn DeclaredCounter::value(self : Self) -> Int {
self.0
}
test "declared functions" {
@test.assert_eq(declared_add(1, 2), 3)
@test.assert_eq(DeclaredCounter(4).value(), 4)
}
宣言された関数に実装がある場合、宣言と実装は関数名、可視性、型パラメータ、パラメータ、戻り値の型、エフェクトについて一致している必要があります。
ローカル関数#
ローカル関数は名前付きにも無名にもできます。ローカル関数定義では型注釈を省略でき、多くの場合自動で推論されます。例えば:
fn local_1() -> Int {
fn inc(x) { // named as `inc`
x + 1
}
// anonymous, instantly applied to integer literal 6
(fn(x) { x + inc(2) })(6)
}
test {
assert_eq(local_1(), 9)
}
単純な無名関数には、MoonBit は arrow function と呼ばれる非常に簡潔な構文を提供します:
[1, 2, 3].eachi((i, x) => println("\{i} => \{x}"))
// parenthesis can be omitted when there is only one parameter
[1, 2, 3].each(x => println(x * x))
ローカル関数では引数型と戻り値型の推論はできますが、effect 推論 は arrow function 構文でのみサポートされます。fn が error を送出 したり 非同期処理を実行 したりする場合は、raise または async を明示的に付ける必要があります。
関数は、名前付きでも無名でも _lexical closure_ です。ローカル束縛を持たない識別子は、外側の字句スコープにある束縛を参照しなければなりません。例えば:
let global_y = 3
fn local_2(x : Int) -> (Int, Int) {
fn inc() {
x + 1
}
fn four() {
global_y + 1
}
(inc(), four())
}
test {
@test.assert_eq(local_2(3), (4, 4))
}
ローカル関数は、自分自身と、先に定義された他のローカル関数だけを参照できます。相互再帰するローカル関数を定義するには、代わりに letrec f = .. and g = .. の構文を使います:
fn f(x) {
// `f` can refer to itself here, but cannot use `g`
if x > 0 {
f(x - 1)
}
}
fn g(x) {
// `g` can refer to `f` and `g` itself
if x < 0 {
f(-x)
} else {
f(x)
}
}
// mutually recursive local functions
letrec even = x => x == 0 || odd(x - 1)
and odd = x => x != 0 && even(x - 1)
関数適用#
関数は、括弧内の引数リストに適用できます:
add3(1, 2, 7)
これは、add3 が前の例のように名前付き関数として定義されていても、下の例のように関数値に束縛された変数であっても同じです:
test {
let add3 = fn(x, y, z) { x + y + z }
assert_eq(add3(1, 2, 7), 10)
}
式 add3(1, 2, 7) は 10 を返します。関数値として評価される式なら、どれでも適用できます:
test {
let f = fn(x) { x + 1 }
let g = fn(x) { x + 2 }
let w = (if true { f } else { g })(3)
assert_eq(w, 4)
}
部分適用#
部分適用は、関数の一部の引数だけを与えて、残りの引数を受け取る新しい関数を得る手法です。MoonBit では、関数適用で _ 演算子を使うことで部分適用できます:
fn add(x : Int, y : Int) -> Int {
x + y
}
test {
let add10 : (Int) -> Int = x => add(10, x)
println(add10(5)) // prints 15
println(add10(10)) // prints 20
}
_ 演算子は、括弧内で欠けている引数を表します。部分適用では同じ括弧内に複数の _ を使えます。例えば Array::fold(_, _, init=5) は fn(x, y) { Array::fold(x, y, init=5) } と等価です。
_ 演算子は、enum の生成、ドット記法の関数呼び出し、パイプラインでも使えます。
警告
部分適用に f(a, _, b) という構文を使うことは非推奨です。代わりに x => f(a, x, b) を使ってください。
ラベル付き引数#
トップレベル関数は、label~ : Type という構文でラベル付き引数を宣言できます。label は関数本体内での引数名にもなります:
fn labelled_1(arg1~ : Int, arg2~ : Int) -> Int {
arg1 + arg2
}
ラベル付き引数は label=arg の構文で渡せます。label=label は label~ と省略できます:
test {
let arg1 = 1
assert_eq(labelled_1(arg2=2, arg1~), 3)
}
ラベル付き引数は任意の順序で渡せます。引数の評価順は、関数宣言におけるパラメータ順と同じです。
Optional arguments#
引数は、label?: Type = default_expr という構文でデフォルト式を与えることで省略可能にできます。default_expr は省略してもかまいません。この引数が呼び出し時に与えられない場合、デフォルト式が使われます:
fn optional(opt? : Int = 42) -> Int {
opt
}
test {
assert_eq(optional(), 42)
assert_eq(optional(opt=0), 0)
}
デフォルト式は、使われるたびに毎回評価されます。デフォルト式に副作用があれば、それも実行されます。例えば:
fn incr(counter? : Ref[Int] = { val: 0 }) -> Ref[Int] {
counter.val = counter.val + 1
counter
}
test {
@test.assert_eq(incr().val, 1)
@test.assert_eq(incr().val, 1)
let counter : Ref[Int] = { val: 0 }
@test.assert_eq(incr(counter~).val, 1)
@test.assert_eq(incr(counter~).val, 2)
}
省略可能引数の値は、呼び出し側では通常の式です。raise や async の文脈では、エラーを送出する可能性がある式や非同期関数呼び出しを渡せます:
fn may_fail(x : Int) -> Int raise Failure {
if x < 0 {
fail("negative")
}
x
}
fn add_with_optional(base : Int, extra? : Int = 1) -> Int {
base + extra
}
test {
inspect(add_with_optional(1, extra=may_fail(2)), content="3")
}
非同期関数では、省略可能引数の式でも通常どおり非同期関数を呼び出せます:
///|
async fn fetch_default() -> Int {
...
}
///|
async fn build(x? : Int = fetch_default()) -> Int {
...
}
///|
async fn use_value() -> Int {
build(x=fetch_default())
}
デフォルト式の結果を複数の関数呼び出しで共有したい場合は、デフォルト式をトップレベルの let 宣言に持ち上げられます:
let default_counter : Ref[Int] = { val: 0 }
fn incr_2(counter? : Ref[Int] = default_counter) -> Int {
counter.val = counter.val + 1
counter.val
}
test {
assert_eq(incr_2(), 1)
assert_eq(incr_2(), 2)
}
デフォルト式は、前の引数に依存することもできます。例えば:
fn create_rectangle(a : Int, b? : Int = a) -> (Int, Int) {
(a, b)
}
test {
debug_inspect(create_rectangle(10), content="(10, 10)")
}
デフォルト値のない省略可能引数#
ユーザーが値を指定しない場合に別の意味を持たせたいことはよくあります。デフォルト値のない省略可能引数は型 T? を持ち、既定値は None です。この種の省略可能引数を直接渡すと、MoonBit は値を自動的に Some で包みます:
fn new_image(width? : Int, height? : Int) -> Image {
if width is Some(w) {
...
}
...
}
let img2 : Image = new_image(width=1920, height=1080)
ときには、T? 型の値をそのまま渡したい場合もあります。たとえば省略可能引数を転送するときです。MoonBit ではこれ用に label?=value という構文があり、label? は label?=label の省略形です:
fn image(width? : Int, height? : Int) -> Image {
...
}
fn fixed_width_image(height? : Int) -> Image {
image(width=1920, height?)
}
自動補完引数#
MoonBit では、関数呼び出しのソース位置のように、特定の型の引数を呼び出し箇所ごとに自動で埋められます。自動補完引数を宣言するには、ラベル付き引数を宣言し、関数属性 #callsite(autofill(param_a, param_b)) を追加するだけです。引数が明示的に与えられない場合、MoonBit は呼び出し箇所で自動的に補完します。
現在 MoonBit がサポートする自動補完引数は 2 種類です。SourceLoc は関数呼び出し全体のソース位置、ArgsLoc は各引数のソース位置を(あれば)含む配列です:
#callsite(autofill(loc, args_loc))
fn f(_x : Int, loc~ : SourceLoc, args_loc~ : ArgsLoc) -> String {
(
$|loc of whole function call: \{loc}
$|loc of arguments: \{args_loc}
)
// loc of whole function call: <filename>:7:3-7:10
// loc of arguments: [Some(<filename>:7:5-7:6), Some(<filename>:7:8-7:9), None, None]
}
自動補完引数は、デバッグ用やテスト用のユーティリティを書くのにとても便利です。
関数エイリアス#
MoonBit では、関数エイリアスを使って関数を別名で呼び出せます。関数エイリアスは次のように宣言します:
#alias(g)
#alias(h, visibility="pub")
fn k() -> Bool {
true
}
visibility フィールドを使うと、可視性の異なる関数エイリアスも作れます。
制御構造#
条件式#
条件式は、条件、then 節、そして省略可能な else 節または else if 節から構成されます。
if x == y {
expr1
} else if x == z {
expr2
} else {
expr3
}
then 節を囲む中括弧は必須です。
条件式は MoonBit では常に値を返し、then 節と else 節の戻り値は同じ型でなければならないことに注意してください。例を示します:
let initial = if size < 1 { 1 } else { size }
else 節を省略できるのは、戻り値の型が Unit の場合だけです。
match 式#
match 式は条件式に似ていますが、どの分岐を評価するかを決めるために pattern matching を使い、同時に変数の抽出も行います。
fn decide_sport(weather : String, humidity : Int) -> String {
match weather {
"sunny" => "tennis"
"rainy" => if humidity > 80 { "swimming" } else { "football" }
_ => "unknown"
}
}
test {
assert_eq(decide_sport("sunny", 0), "tennis")
}
可能な条件が省略されていると、コンパイラは警告を出し、そのケースに到達するとプログラムは終了します。
guard 文#
guard 文は、指定した不変条件を確認するために使います。不変条件が満たされていれば、プログラムは後続の文の実行を続け、そのまま戻ります。条件が満たされない場合、つまり false の場合は、else ブロック内のコードが実行され、その評価結果が返されます(後続の文はスキップされます)。
fn guarded_get(array : Array[Int], index : Int) -> Int? {
guard index >= 0 && index < array.length() else { None }
Some(array[index])
}
test {
debug_inspect(guarded_get([1, 2, 3], -1), content="None")
}
guard 文と is 式#
let 文は pattern matching と組み合わせて使えます。しかし、let 文で扱えるのは 1 つのケースだけです。ここで guard 文と is 式 を組み合わせると、この問題を解決できます。
次の例では、getProcessedText は入力 path がすべてプレーンテキストであるリソースを指すことを前提にし、プレーンテキストのリソースを取り出しながら guard 文でこの不変条件を保証しています。match 文を使う場合と比べて、その後の text の処理は 1 段階浅いインデントで書けます。
enum Resource {
Folder(Array[String])
PlainText(String)
JsonConfig(Json)
}
fn getProcessedText(
resources : Map[String, Resource],
path : String,
) -> String raise Error {
guard resources.get(path) is Some(resource) else { fail("\{path} not found") }
guard resource is PlainText(text) else { fail("\{path} is not plain text") }
process(text)
}
else 部分を省略すると、guard 文で指定した条件が真でないか、またはマッチできない場合にプログラムは終了します。
guard condition // <=> guard condition else { panic() }
guard expr is Some(x)
// <=> guard expr is Some(x) else { _ => panic() }
while ループ#
MoonBit では、while ループを使うと、条件が真である限りコードブロックを繰り返し実行できます。条件はコードブロックを実行する前に評価されます。while ループは while キーワードの後に条件とループ本体を続けて定義します。ループ本体は文の列であり、条件が真である限り実行されます。
fn main {
let mut i = 5
while i > 0 {
println(i)
i = i - 1
}
}
5
4
3
2
1
ループ本体では break と continue を使えます。break を使うと現在のループを抜けられ、continue を使うと現在の反復の残りを飛ばして次の反復に進みます。
fn main {
let mut i = 5
while i > 0 {
i = i - 1
if i == 4 {
continue
}
if i == 1 {
break
}
println(i)
}
}
3
2
while ループは省略可能な nobreak 節もサポートします。ループ条件が false になると nobreak 節が実行され、その後ループは終了します。
fn main {
let mut i = 2
while i > 0 {
println(i)
i = i - 1
} nobreak {
println(i)
}
}
2
1
0
nobreak 節がある場合、while ループは値を返すこともできます。戻り値は nobreak 節の評価結果です。この場合、break でループを抜けるなら break の後に戻り値を指定する必要があり、その型は nobreak 節の戻り値と同じでなければなりません。
fn main {
let mut i = 10
let r = while i > 0 {
i = i - 1
if i % 2 == 0 {
break 5
}
} nobreak {
7
}
println(r)
}
5
fn main {
let mut i = 10
let r = while i > 0 {
i = i - 1
} nobreak {
7
}
println(r)
}
7
for ループ#
MoonBit は C 言語風の for ループもサポートします。for キーワードの後に、変数初期化節、ループ条件、更新節がセミコロンで区切られて続きます。これらは括弧で囲む必要はありません。例えば、次のコードは新しい変数束縛 i を作成し、これはループ全体で有効かつ不変です。これにより、明快なコードを書きやすくなり、理解もしやすくなります:
fn main {
for i = 0; i < 5; i = i + 1 {
println(i)
}
}
0
1
2
3
4
変数初期化節では複数の束縛を作成できます:
for i = 0, j = 0; i + j < 100; i = i + 1, j = j + 1 {
println(i)
}
更新節では、束縛変数が複数ある場合、それらは同時に更新されることに注意してください。つまり、上の例では更新節は i = i + 1、j = j + 1 を順番に実行するのではなく、i と j を同時に増やします。したがって、更新節で束縛変数の値を読むと、常に前回の反復で更新された値が得られます。
変数初期化節、ループ条件、更新節はすべて省略可能です。例えば、次の 2 つは無限ループです:
for i = 1; ; i = i + 1 {
println(i)
}
for ;; {
println("loop forever")
}
for ループも continue、break、nobreak 節をサポートします。while ループと同様に、for ループも break と nobreak 節を使って値を返せます。
continue 文は for ループの現在の反復の残り(更新節を含む)を飛ばして次の反復に進みます。continue 文は、束縛変数の個数に一致する式をカンマ区切りで続けることで、for ループの束縛変数も更新できます。
例えば、次のプログラムは 1 から 6 までの偶数の合計を計算します:
fn main {
let sum = for i = 1, acc = 0; i <= 6; i = i + 1 {
if i % 2 == 0 {
println("even: \{i}")
continue i + 1, acc + i
}
} nobreak {
acc
}
println(sum)
}
even: 2
even: 4
even: 6
12
for .. in ループ#
MoonBit では for .. in ループ構文を使って、さまざまなデータ構造やシーケンスの要素を走査できます:
for x in [1, 2, 3] {
println(x)
}
for .. in ループは MoonBit の標準ライブラリにある Iter の利用に変換されます。.iter() : Iter[T] メソッドを持つ型なら、for .. in で走査できます。Iter 型の詳細は、下の Iterator を参照してください。
for .. in ループは、次のような整数列の反復もサポートします:
test {
let mut i = 0
for j in 0..<10 {
i += j
}
assert_eq(i, 45)
let mut k = 0
for l in 0..<=10 {
k += l
}
assert_eq(k, 55)
}
1 つの値からなるシーケンスに加えて、MoonBit は Map のような 2 つの値からなるシーケンスも、標準ライブラリの Iter2 型を通じて走査できます。.iter2() : Iter2[A, B] メソッドを持つ型なら、2 つのループ変数を使う for .. in で走査できます:
for k, v in { "x": 1, "y": 2, "z": 3 } {
println(k)
println(v)
}
2 つのループ変数を使う for .. in の別の例として、配列の添字を追跡しながら配列を走査することができます:
fn main {
for index, elem in [4, 5, 6] {
let i = index + 1
println("The \{i}-th element of the array is \{elem}")
}
}
The 1-th element of the array is 4
The 2-th element of the array is 5
The 3-th element of the array is 6
return、break、エラー処理などの制御フロー操作は、for .. in ループの本体でも使えます:
fn main {
let map = { "x": 1, "y": 2, "z": 3, "w": 4 }
for k, v in map {
if k == "y" {
continue
}
println("\{k}, \{v}")
if k == "z" {
break
}
}
}
x, 1
z, 3
ループ変数が不要なら、_ で無視できます。
for .. in ループの範囲式#
for .. in ループは範囲式と組み合わせて、数値範囲を反復することもできます:
fn main {
for x in 0..<5 {
println(x)
}
}
0
1
2
3
4
for .. in ループで使える範囲式には 4 種類あります:
a..<b:aからbまで昇順で反復し、bを含まないa..<=b:aからbまで昇順で反復し、bを含むa>..b:aからbまで降順で反復し、aを含まないa>=..b:aからbまで降順で反復し、aを含む
リスト内包表記#
MoonBit は、別のコレクションや範囲を反復してコレクションを構築するリスト内包表記をサポートします:
let squares = [ for x in 1..<=5 => x * x ]
let even_numbers = [ for x in 0..<100 if x % 2 == 0 => x ]
let labelled = [ for i, x in ["a", "b", "c"] => "\{i}: \{x}" ]
let map = { 1: 2, 2: 4, 3: 8 }
let present = [ for x in [1, 2, 3] if map.get(x) is Some(y) => y ]
構文は [ for ... => ... ] です。=> より前の部分は for .. in と同じ反復規則に従います。束縛が 1 つなら Iter、2 つなら Iter2 を使い、0..<10 のような範囲式も使えます。任意の if ガードで、結果式を評価する前に要素をフィルタできます。ガード内の is 式で導入された名前、たとえば上の y は結果式で使えます。
期待される型がない場合、結果は既定で Array[T] になります。期待される型が分かっている場合、リスト内包表記は FixedArray[T]、ReadOnlyArray[T]、String、Bytes、Json も構築できます:
let text : String = [ for x in 0..<3 => (x + 'a').unsafe_to_char() ]
let bytes : Bytes = [ for x in 0..<3 => x.to_byte() ]
let fixed : FixedArray[_] = [ for x in 1..<=3 => x ]
遅延列や無限列には、Iter[T] を直接作成します。次のイテレータは、捕捉した変数にフィボナッチ数列の状態を保持し、収集する前に長さを制限しています:
let mut p1 = 1
let mut p2 = 0
let fib_numbers : Iter[Int] = Iter::new(fn() {
let next = p1
p1 = p1 + p2
p2 = next
Some(next)
})
let first_six = fib_numbers.take(6).collect()
リスト内包表記の内部では、return、break、continue などの制御フロー操作は使えません。
ラベル付き continue/break#
ループにラベルを付けると、ネストしたループの中から break や continue で参照できます。例えば:
test "break label" {
let mut count = 0
let xs = [1, 2, 3]
let ys = [4, 5, 6]
let res = outer~: for i in xs {
for j in ys {
count = count + i
break outer~ j
}
} nobreak {
-1
}
assert_eq(res, 4)
assert_eq(count, 1)
}
test "continue label" {
let mut count = 0
let init = 10
let res = outer~: for i = init {
if i == 0 {
break outer~ 42
}
for ;; {
count = count + 1
continue outer~ i - 1
}
}
assert_eq(res, 42)
assert_eq(count, 10)
}
defer 式#
defer 式は、確実なリソース解放を行うために使えます。defer の構文は次のとおりです:
defer <expr>
<body>
プログラムが body を抜けるたびに、expr が実行されます。例えば、次のプログラムでは:
defer println("perform resource cleanup")
println("do things with the resource")
まず do things with the resource が出力され、その後に perform resource cleanup が出力されます。defer 式は、body がどのように終了しても必ず実行されます。error を扱えるほか、return、break、continue を含む制御フロー構文にも対応します。
連続した defer は逆順で実行されます。例えば、次のコードでは:
defer println("first defer")
defer println("second defer")
println("do things")
まず do things、次に second defer、最後に first defer が出力されます。
defer の右辺では return、break、continue は使えません。現在のところ、defer の右辺でエラーを送出することや async 関数を呼ぶことも禁止されています。
イテレータ#
イテレータは、要素へのアクセスを提供しながらシーケンスを走査するオブジェクトです。Java の Iterator<T> のような従来の OO 言語では、next() と hasNext() を使って反復処理を進めます。一方、関数型言語(JavaScript の forEach、Lisp の mapcar)では、操作とシーケンスを受け取り、その操作をシーケンスに適用しながら消費する高階関数を使います。前者は 外部イテレータ(利用者から見える)、後者は 内部イテレータ(利用者から見えない)と呼ばれます。
組み込み型 Iter[T] は MoonBit の外部イテレータ実装です。next() を公開して次の値を取り出します。Some(value) を返してイテレータを進め、反復が終わると None を返します。ほとんどの組み込み順次データ構造は Iter を実装しています:
///|
fn filter_even(l : Array[Int]) -> Array[Int] {
let l_iter : Iter[Int] = l.iter()
l_iter.filter(x => (x & 1) == 0).collect()
}
///|
fn fact(n : Int) -> Int {
let start = 1
let range : Iter[Int] = start.until(n)
range.fold(Int::mul, init=start)
}
よく使うメソッドには次のようなものがあります:
each: イテレータの各要素を反復し、それぞれに関数を適用します。fold: 与えられた関数でイテレータの要素を畳み込み、与えられた初期値から開始します。collect: イテレータの要素を配列に集めます。filter: lazy 述語関数に基づいてイテレータの要素をフィルタします。map: lazy マッピング関数を使ってイテレータの要素を変換します。concat: lazy 2 つのイテレータを結合し、2 つ目の要素を 1 つ目の後ろに連結します。
filter や map のようなメソッドは、Array のようなシーケンスオブジェクトで非常によく使われます。しかし Iter が特別なのは、新しい Iter を構築するメソッドがすべて lazy であることです(つまり、関数の中に包まれているため、呼び出しただけでは反復は始まりません)。その結果、中間値のための確保が不要になります。これが Iter をシーケンス走査に適したものにしている理由で、余分なコストがありません。MoonBit では、シーケンスオブジェクトそのものではなく Iter を関数間で受け渡すことを推奨します。
Array のような定義済みのシーケンス構造とそのイテレータだけで、たいていは十分です。しかし、要素型 S の独自シーケンスでこれらのメソッドを活用するには、Iter を実装する必要があります。つまり、Iter[S] を返す関数です。Bytes を例に取ると:
///|
fn iter(data : Bytes) -> Iter[Byte] {
let mut index = 0
Iter::new(fn() -> Byte? {
if index < data.length() {
let byte = data[index]
index += 1
Some(byte)
} else {
None
}
})
}
イテレータは 1 回限りです。next() を呼ぶか、each、fold、collect などのメソッドで消費すると、内部状態は進み、リセットできません。同じシーケンスをもう一度走査したい場合は、元のソースから新しい Iter を取得してください。
カスタムデータ型#
新しいデータ型を作る方法は struct と enum の 2 つです。
struct#
MoonBit の struct は tuple に似ていますが、フィールドは名前で管理されます。struct は struct リテラルで構築でき、ラベル付きの値の集合を中括弧で囲みます。フィールドが型定義と完全に一致していれば、struct リテラルの型は自動推論されます。フィールドにはドット構文 s.f でアクセスできます。フィールドに mut キーワードが付いていれば、新しい値を代入できます。
struct User {
id : Int
name : String
mut email : String
}
fn main {
let u = User::{ id: 0, name: "John Doe", email: "john@doe.com" }
u.email = "john@doe.name"
//! u.id = 10
println(u.id)
println(u.name)
println(u.email)
}
0
John Doe
john@doe.name
省略記法で struct を構築する#
name や email のような変数がすでにあるなら、struct を構築するときにそれらの名前を繰り返すのは冗長です。代わりに省略記法を使えます。振る舞いはまったく同じです:
let name = "john"
let email = "john@doe.com"
let u = User::{ id: 0, name, email }
同じフィールドを持つ他の struct がないなら、構築時に struct 名を付けるのは冗長です:
let u2 = { id: 0, name, email }
struct 更新構文#
既存の struct を元にして、いくつかのフィールドだけ更新した新しい struct を作ると便利です。
fn main {
let user = { id: 0, name: "John Doe", email: "john@doe.com" }
let updated_user = { ..user, email: "john@doe.name" }
println(
(
$|{ id: \{user.id}, name: \{user.name}, email: \{user.email} }
$|{ id: \{updated_user.id}, name: \{updated_user.name}, email: \{updated_user.email} }
),
)
}
{ id: 0, name: John Doe, email: john@doe.com }
{ id: 0, name: John Doe, email: john@doe.name }
struct のカスタムコンストラクタ#
MoonBit では、すべての struct 型に対してカスタムコンストラクタも定義できます。コンストラクタは struct 名で呼び出して値を作成できる特別なメソッドです。まず通常どおり struct を定義します:
struct IntBox {
value : Int
} derive(Debug)
次に、struct 型と同じ名前のメソッドとしてコンストラクタを実装します。戻り値はその struct 自身でなければなりません:
fn IntBox::IntBox(value : Int) -> IntBox {
{ value, }
}
struct がコンストラクタを宣言していれば、名前で直接構築できます:
let box = IntBox(10)
debug_inspect(box, content="{ value: 10 }")
コンストラクタ呼び出しはコンストラクタメソッドのシグネチャに従うため、ラベルのない引数は馴染みのある TypeName(value) 形式で書けます。
コンストラクタでも、通常の関数と同じようにラベル付き引数やオプション引数を使えます:
struct StructWithConstr {
x : Int
y : Int
} derive(Debug)
fn StructWithConstr::StructWithConstr(x~ : Int, y? : Int = x) -> StructWithConstr {
{ x, y }
}
let s = StructWithConstr(x=1)
debug_inspect(s, content="{ x: 1, y: 1 }")
struct コンストラクタは通常の関数として実装されるため、エラーを送出することもできます:
suberror BuildError {
NegativeInput
} derive(Debug)
struct Positive {
value : Int
} derive(Debug)
fn Positive::Positive(x : Int) -> Positive raise BuildError {
guard x >= 0 else { raise NegativeInput }
{ value: x }
}
try Positive(10) catch {
error => debug_inspect(error, content="NegativeInput")
} noraise {
value => debug_inspect(value, content="{ value: 10 }")
}
try Positive(-1) catch {
error => debug_inspect(error, content="NegativeInput")
} noraise {
value => debug_inspect(value, content="{ value: -1 }")
}
非同期コンストラクタは async fn TypeName::TypeName として宣言し、非同期コードの中で使えます:
struct AsyncBox {
value : Int
} derive(Debug)
async fn AsyncBox::AsyncBox(x : Int) -> AsyncBox {
@async.sleep(0)
{ value: x }
}
async test "struct constructor async" {
let box = AsyncBox(10)
debug_inspect(box, content="{ value: 10 }")
}
struct コンストラクタで値を作成することは、enum コンストラクタ とまったく同じ意味を持ちます。ただし、struct コンストラクタはパターンマッチには使えません。例えば、外部パッケージの struct をコンストラクタで作るとき、式の期待型が分かっていればパッケージ名は省略できます。
struct コンストラクタは通常の関数として実装されるため、エラーを送出したり、非同期操作を実行したりできます。struct コンストラクタは optional arguments もサポートします。オプション引数のデフォルト値は、通常の関数シグネチャと同じようにコンストラクタ実装に書きます。
Enum#
enum 型は、関数型言語における代数的データ型に似ています。C/C++ に慣れている方は tagged union と呼ぶほうがしっくりくるかもしれません。
enum には複数のケース(コンストラクタ)を持てます。コンストラクタ名は大文字で始める必要があります。これらの名前を使って enum の対応するケースを構築したり、パターンマッチで enum 値がどの分岐に属するかを判定したりできます:
/// An enum type that represents the ordering relation between two values,
/// with three cases "Smaller", "Greater" and "Equal"
enum Relation {
Smaller
Greater
Equal
}
/// compare the ordering relation between two integers
fn compare_int(x : Int, y : Int) -> Relation {
if x < y {
// when creating an enum, if the target type is known,
// you can write the constructor name directly
Smaller
} else if x > y {
// but when the target type is not known,
// you can always use `TypeName::Constructor` to create an enum unambiguously
Relation::Greater
} else {
Equal
}
}
/// output a value of type `Relation`
fn print_relation(r : Relation) -> Unit {
// use pattern matching to decide which case `r` belongs to
match r {
// during pattern matching, if the type is known,
// writing the name of constructor is sufficient
Smaller => println("smaller!")
// but you can use the `TypeName::Constructor` syntax
// for pattern matching as well
Relation::Greater => println("greater!")
Equal => println("equal!")
}
}
fn main {
print_relation(compare_int(0, 1))
print_relation(compare_int(1, 1))
print_relation(compare_int(2, 1))
}
smaller!
equal!
greater!
enum のケースは payload データも持てます。ここでは、整数リスト型を enum で定義する例を示します:
enum Lst {
Nil
// constructor `Cons` carries additional payload: the first element of the list,
// and the remaining parts of the list
Cons(Int, Lst)
}
// In addition to binding payload to variables,
// you can also continue matching payload data inside constructors.
// Here's a function that decides if a list contains only one element
fn is_singleton(l : Lst) -> Bool {
match l {
// This branch only matches values of shape `Cons(_, Nil)`,
// i.e. lists of length 1
Cons(_, Nil) => true
// Use `_` to match everything else
_ => false
}
}
fn print_list(l : Lst) -> Unit {
// when pattern-matching an enum with payload,
// in additional to deciding which case a value belongs to
// you can extract the payload data inside that case
match l {
Nil => println("nil")
// Here `x` and `xs` are defining new variables
// instead of referring to existing variables,
// if `l` is a `Cons`, then the payload of `Cons`
// (the first element and the rest of the list)
// will be bind to `x` and `xs
Cons(x, xs) => {
println("\{x},")
print_list(xs)
}
}
}
fn main {
// when creating values using `Cons`, the payload of by `Cons` must be provided
let l : Lst = Cons(1, Cons(2, Nil))
println(is_singleton(l))
print_list(l)
}
false
1,
2,
nil
ラベル付き引数を持つコンストラクタ#
enum コンストラクタはラベル付き引数を持てます:
enum E {
// `x` and `y` are labelled argument
C(x~ : Int, y~ : Int)
}
// pattern matching constructor with labelled arguments
fn f(e : E) -> Unit {
match e {
// `label=pattern`
C(x=0, y=0) => println("0!")
// `x~` is an abbreviation for `x=x`
// Unmatched labelled arguments can be omitted via `..`
C(x~, ..) => println(x)
}
}
fn main {
f(C(x=0, y=0))
let x = 0
f(C(x~, y=1)) // <=> C(x=x, y=1)
}
0!
0
パターンマッチでは、コンストラクタのラベル付き引数にも struct フィールドのようにアクセスできます:
enum Object {
Point(x~ : Double, y~ : Double)
Circle(x~ : Double, y~ : Double, radius~ : Double)
}
suberror NotImplementedError derive(Debug)
fn Object::distance_with(
self : Object,
other : Object,
) -> Double raise NotImplementedError {
match (self, other) {
// For variables defined via `Point(..) as p`,
// the compiler knows it must be of constructor `Point`,
// so you can access fields of `Point` directly via `p.x`, `p.y` etc.
(Point(_) as p1, Point(_) as p2) => {
let dx = p2.x - p1.x
let dy = p2.y - p1.y
(dx * dx + dy * dy).sqrt()
}
(Point(_), Circle(_)) | (Circle(_), Point(_)) | (Circle(_), Circle(_)) =>
raise NotImplementedError
}
}
fn main {
let p1 : Object = Point(x=0, y=0)
let p2 : Object = Point(x=3, y=4)
let c1 : Object = Circle(x=0, y=0, radius=2)
try {
println(p1.distance_with(p2))
println(p1.distance_with(c1))
} catch {
_ => println("NotImplementedError")
}
}
5
NotImplementedError
可変フィールドを持つコンストラクタ#
コンストラクタに可変フィールドを定義することもできます。これは命令的なデータ構造を定義するときに特に便利です:
// A set implemented using mutable binary search tree.
struct Set[X] {
mut root : Tree[X]
}
fn[X : Compare] Set::insert(self : Set[X], x : X) -> Unit {
self.root = self.root.insert(x, parent=Nil)
}
// A mutable binary search tree with parent pointer
enum Tree[X] {
Nil
// only labelled arguments can be mutable
Node(
mut value~ : X,
mut left~ : Tree[X],
mut right~ : Tree[X],
mut parent~ : Tree[X]
)
}
// In-place insert a new element to a binary search tree.
// Return the new tree root
fn[X : Compare] Tree::insert(
self : Tree[X],
x : X,
parent~ : Tree[X],
) -> Tree[X] {
match self {
Nil => Node(value=x, left=Nil, right=Nil, parent~)
Node(_) as node => {
let order = x.compare(node.value)
if order == 0 {
// mutate the field of a constructor
node.value = x
} else if order < 0 {
// cycle between `node` and `node.left` created here
node.left = node.left.insert(x, parent=node)
} else {
node.right = node.right.insert(x, parent=node)
}
// The tree is non-empty, so the new root is just the original tree
node
}
}
}
拡張可能 enum#
extenum はオープンな enum 型を定義します。通常の enum とは異なり、extenum には後から、別パッケージからのものも含めて、さらにコンストラクタを追加できます。これは、あるパッケージが共有のイベント、メッセージ、または拡張ポイントとなる型を定義し、他のパッケージがそれぞれ独自のケースを提供したい場合に有用です。
pub(all) extenum LogEvent[T] {
Info(T)
}
同じパッケージ内の拡張可能 enum にコンストラクタを追加するには、extenum Type += { ... } を使います:
pub(all) extenum LogEvent[T] += {
Warning(T)
Critical(T, T)
}
別パッケージの拡張可能 enum を拡張するには、その型を定義しているパッケージで対象の型を修飾します:
pub(all) extenum @base.LogEvent[T] += {
Debug(T)
}
拡張可能 enum のコンストラクタは、そのコンストラクタを定義したパッケージで修飾されます。現在のパッケージのコンストラクタについては、期待される型が分かっている場合、コンストラクタ名をそのまま使用できます。別パッケージのコンストラクタについては、式やパターンの中で @pkg.Constructor を使います。拡張可能 enum 型とコンストラクタの由来の両方を明示したい場合は、コンストラクタを @type_pkg.Type::@constructor_pkg.Constructor と書きます。
あるパッケージが基底パッケージと拡張パッケージの両方を import すると、両方のパッケージから来る値は同じ拡張可能 enum 型を持ちます:
pub fn describe(event : @base.LogEvent[String]) -> String {
match event {
@base.Info(message) => "info: \{message}"
@base.Warning(message) => "warning: \{message}"
@base.Critical(code, message) => "critical \{code}: \{message}"
@plugin.Debug(message) => "debug: \{message}"
_ => "unknown"
}
}
pub fn debug_event(message : String) -> @base.LogEvent[String] {
@plugin.Debug(message)
}
pub fn qualified_debug_event(message : String) -> @base.LogEvent[String] {
@base.LogEvent::@plugin.Debug(message)
}
現在の宣言の外側でさらにコンストラクタを追加できるため、パターンマッチにはワイルドカード分岐を含める必要があります。
拡張できるのは extenum 宣言だけです。通常の enum 宣言は閉じています。
tuple struct#
MoonBit では、tuple struct と呼ばれる特別な struct をサポートしています:
struct UserId(Int)
struct UserInfo(UserId, String)
tuple struct は、コンストラクタが 1 つだけの enum に似ています(そのコンストラクタ名は tuple struct 自身の名前と同じです)。そのため、コンストラクタで値を作成したり、パターンマッチで内部表現を取り出したりできます:
fn main {
let id : UserId = UserId(1)
let name : UserInfo = UserInfo(id, "John Doe")
let UserId(uid) = id // uid : Int
let UserInfo(_, uname) = name // uname: String
println(uid)
println(uname)
}
1
John Doe
パターンマッチのほかにも、タプルと同様にインデックスで要素へアクセスできます:
fn main {
let id : UserId = UserId(1)
let info : UserInfo = UserInfo(id, "John Doe")
let uid : Int = id.0
let uname : String = info.1
println(uid)
println(uname)
}
1
John Doe
型エイリアス#
MoonBit では type NewType = OldType という構文で型エイリアスを定義できます:
警告
古い構文 typealias OldType as NewType は将来削除される可能性があります。
pub type Index = Int
pub type MyIndex = Int
pub type MyMap = Map[Int, String]
上記の他の型宣言とは異なり、型エイリアスは新しい型を定義しません。定義とまったく同じように振る舞う型マクロにすぎません。そのため、型エイリアスに対して新しいメソッドを定義したり、trait を実装したりすることはできません。
Tip
型エイリアスは、段階的なコードリファクタリングに使えます。
たとえば、型 T を @pkgA から @pkgB に移したい場合、@pkgA に type T = @pkgB.T という型エイリアスを残しておき、@pkgA.T の使用箇所を 段階的に @pkgB.T へ移していけます。@pkgA.T の使用がすべて @pkgB.T に移行したあとで、型エイリアスを削除できます。
ローカル型#
MoonBit では、トップレベル関数の先頭で struct / enum を宣言できます。これらは現在のトップレベル関数の内部でのみ可視です。ローカル型はトップレベル関数のジェネリックパラメータを使えますが、追加のジェネリックパラメータを自分で導入することはできません。ローカル型は derive を使ってメソッドを導出できますが、手動で追加のメソッドを定義することはできません。例えば:
fn[T : Debug] toplevel(x : T) -> Unit {
enum LocalEnum {
A(T)
B(Int)
} derive(Debug)
struct LocalStruct {
a : (String, T)
} derive(Debug)
struct LocalStructTuple(T) derive(Debug)
...
}
現在、ローカル型をエラー型として宣言することはできません。
パターンマッチング#
パターンマッチングを使うと、特定のパターンに一致させて、データ構造からデータを束縛できます。
単純なパターン#
式を次のものに対してパターンマッチできます:
真偽値、数値、文字、文字列などのリテラル
定数
struct
enum
配列
map
JSON
などです。一致した値を後で使えるように、識別子を定義して束縛することもできます。
const ONE = 1
fn match_int(x : Int) -> Unit {
match x {
0 => println("zero")
ONE => println("one")
value => println(value)
}
}
関心のない値には _ をワイルドカードとして使えます。また、struct・enum・配列の残りのフィールドを無視するには .. を使えます(array pattern を参照)。
struct Point3D {
x : Int
y : Int
z : Int
}
fn match_point3D(p : Point3D) -> Unit {
match p {
{ x: 0, .. } => println("on yz-plane")
_ => println("not on yz-plane")
}
}
enum Point[T] {
Point2D(Int, Int, name~ : String, payload~ : T)
}
fn[T] match_point(p : Point[T]) -> Unit {
match p {
//! Point2D(0, 0) => println("2D origin")
Point2D(0, 0, ..) => println("2D origin")
Point2D(_) => println("2D point")
_ => panic()
}
}
特定のパターンに名前を付けるには as を使えます。また、| を使うと複数のケースを一度にマッチできます。1 つのパターン内で同じ変数名を 2 回束縛することはできず、| パターンの両側では同じ変数集合が束縛されている必要があります。
match expr {
//! Add(e1, e2) | Lit(e1) => ...
Lit(n) as a => ...
Add(e1, e2) | Mul(e1, e2) => ...
...
}
配列パターン#
配列パターンは、次の型に対してパターンマッチし、それぞれの要素やビューを取り出すために使えます:
Type |
要素 |
ビュー |
|---|---|---|
Array[T], ArrayView[T], FixedArray[T] |
T |
ArrayView[T] |
Bytes, BytesView |
Byte |
BytesView |
String, StringView |
Char |
StringView |
配列パターンには次の形があります:
[]: matching for empty array[pa, pb, pc]: matching for array of length three, and bindpa,pb,pcto the three elements[pa, ..rest, pb]: matching for array with at least two elements, and bindpato the first element,pbto the last element, andrestto the remaining elements. the binderrestcan be omitted if the rest of the elements are not needed. Arbitrary number of elements are allowed preceding and following the..part. Because..can match uncertain number of elements, it can appear at most once in an array pattern.
test {
let ary = [1, 2, 3, 4]
if ary is [a, b, .. rest] && a == 1 && b == 2 && rest.length() == 2 {
inspect("a = \{a}, b = \{b}", content="a = 1, b = 2")
} else {
fail("")
}
guard ary is [.., a, b] else { fail("") }
inspect("a = \{a}, b = \{b}", content="a = 3, b = 4")
}
配列パターンは、文字列を Unicode 安全に操作する方法を提供します。つまり、コードユニットの境界を尊重します。たとえば、文字列が回文かどうかを調べられます:
test {
fn palindrome(s : String) -> Bool {
for view = s.view() {
match view {
[] | [_] => break true
[a, .. rest, b] => if a == b { continue rest } else { break false }
}
}
}
inspect(palindrome("abba"), content="true")
inspect(palindrome("中b中"), content="true")
inspect(palindrome("文bb中"), content="false")
}
配列パターンの中に連続した char または byte 定数がある場合、パターンのスプレッド .. 演算子でそれらをまとめて、コードをすっきりさせられます。なおこの場合、文字列または bytes 定数の後に続く .. は要素数ちょうどに一致するため、1 回しか使えないという制約はありません。
const NO : Bytes = b"no"
test {
fn match_string(s : String) -> Bool {
match s {
[.. "yes", ..] => true // equivalent to ['y', 'e', 's', ..]
}
}
fn match_bytes(b : Bytes) -> Bool {
match b {
[.. NO, ..] => false // equivalent to ['n', 'o', ..]
}
}
}
ビット列パターン#
ビット列パターンは、バイトコンテナから詰め込まれたビットフィールドをマッチできます。BytesView、Bytes、Array[Byte]、FixedArray[Byte]、ReadOnlyArray[Byte]、ArrayView[Byte] でサポートされています。エンディアンを明確にするために、be / le 接尾辞で幅を明示してください。be は 1..64 の幅をサポートし、le はバイト境界に揃った幅(8 * n)に対してのみ定義されます。これは little-endian の順序がバイト単位で定義されるためです。.. がない場合、パターンはビュー全体を消費しなければなりません。
test {
let packet : Bytes = b"\xD2\x10\x7F"
let header : BytesView = packet[0:2]
let (flag, kind, version, length) = match header {
[u1be(flag), u3be(kind), u4be(version), u8be(length)] =>
(flag, kind, version, length)
_ => fail("bad header")
}
assert_eq(flag, 1)
assert_eq(kind, 0b101)
assert_eq(version, 0b0010)
assert_eq(length, 16)
}
リテラルのビットパターンを使ってヘッダを検証し、.. で残りのデータを次のパースステップに渡せます。
test {
let data : Bytes = b"\xF1\xAA\xBB"
let view : BytesView = data[0:]
let tag = match view {
[u4be(0b1111), u4be(tag), .. rest] => {
assert_eq(rest, b"\xAA\xBB"[0:])
tag
}
_ => fail("bad prefix")
}
assert_eq(tag, 0b0001)
}
一般的なバイトコンテナでの例(MutArrayView スライスに注意):
test {
let b : Bytes = b"\x80"
guard b is [u1be(1), ..] else { fail("Bytes") }
let a : Array[Byte] = [b'\x80']
guard a is [u1be(1), ..] else { fail("Array[Byte]") }
let f : FixedArray[Byte] = [b'\x80']
guard f is [u1be(1), ..] else { fail("FixedArray[Byte]") }
let r : ReadOnlyArray[Byte] = [b'\x80']
guard r is [u1be(1), ..] else { fail("ReadOnlyArray[Byte]") }
let v : ArrayView[Byte] = a[:]
guard v is [u1be(1), ..] else { fail("ArrayView[Byte]") }
let mv : MutArrayView[Byte] = a.mut_view()
guard mv[:] is [u1be(1), ..] else { fail("MutArrayView[Byte]") }
}
符号付きパターンは 2 の補数の意味で解釈されます。たとえば、u1be は 0 または 1 を返し、i1be は 0 または -1 を返します:
test {
let bytes = b"\x80"
let u : UInt = match bytes[:] {
[u1be(u), ..] => u
_ => fail("u1be")
}
let i : Int = match bytes[:] {
[i1be(i), ..] => i
_ => fail("i1be")
}
assert_eq(u, 1U)
assert_eq(i, -1)
}
結果型は幅によって異なります:
幅 |
結果型 |
|---|---|
1..32 bits ( |
|
33..64 bits ( |
|
33..64 bits ( |
|
範囲パターン#
組み込みの整数型と Char では、値が特定の範囲に入るかどうかを MoonBit でマッチできます。
範囲パターンは a..<b または a..=b の形をとります。..< は上限を含まないことを、..= は上限を含むことを意味します。a と b には次のいずれかを指定できます:
リテラル
constで宣言された名前付き定数_は、この側に制約がないことを意味します
いくつか例を示します:
const Zero = 0
fn sign(x : Int) -> Int {
match x {
_..<Zero => -1
Zero => 0
1..<_ => 1
}
}
fn classify_char(c : Char) -> String {
match c {
'a'..='z' => "lowercase"
'A'..='Z' => "uppercase"
'0'..='9' => "digit"
_ => "other"
}
}
Map パターン#
MoonBit では、map のようなデータ構造に対して便利にマッチできます。map パターンの中では、key : value 構文は key が map に存在する場合に一致し、key の値をパターン value でマッチします。key? : value 構文は key の有無にかかわらず一致し、value は map[key](Optional)と照合されます。
match map {
// matches if any only if "b" exists in `map`
{ "b": _, .. } => ...
// matches if and only if "b" does not exist in `map` and "a" exists in `map`.
// When matches, bind the value of "a" in `map` to `x`
{ "b"? : None, "a": x, .. } => ...
// compiler reports missing case: { "b"? : None, "a"? : None }
}
To match a data type
Tusing map pattern,Tmust have a methodget(Self, K) -> Option[V]for some typeKandV(see method and trait).現在、map パターンの key 部分はリテラルまたは定数でなければなりません
map パターンは常に open です。マッチしなかった key は黙って無視され、この性質を明示するには
..を追加する必要がありますmap パターンは効率的なコードにコンパイルされます。各 key は高々 1 回しか取得されません
Json パターン#
マッチ対象の値の型が Json の場合は、リテラルパターンをコンストラクタと組み合わせて直接使えます:
match json {
{ "version": "1.0.0", "import": [..] as imports, .. } => ...
{ "version": Number(i, ..), "import": Array(imports), .. } => ...
...
}
ガード条件#
パターンマッチ式の各 case にはガード条件を付けられます。ガード条件は、case がマッチするために真でなければならない真偽式です。ガード条件が偽なら、その case はスキップされ、次の case が試されます。例えば:
fn guard_cond(x : Int?) -> Int {
fn f(x : Int) -> Array[Int] {
[x, x + 42]
}
match x {
Some(a) if f(a) is [0, b] => a + b
Some(b) => b
None => -1
}
}
test {
assert_eq(guard_cond(None), -1)
assert_eq(guard_cond(Some(0)), 42)
assert_eq(guard_cond(Some(1)), 1)
}
ガード条件は、match 式ですべてのパターンが網羅されているかをチェックするときには考慮されません。そのため、次の case では partial match の警告が表示されます:
fn guard_check(x : Int?) -> Unit {
match x {
Some(a) if a >= 0 => ()
Some(a) if a < 0 => ()
None => ()
}
}
警告
ガード条件の中で、マッチ対象の値の一部を変更する関数を呼ぶことは推奨されません。そのような場合、変更された部分は後続のパターンで再評価されません。注意して使ってください。
ジェネリクス#
ジェネリクスは、トップレベル関数とデータ型の定義でサポートされています。型パラメータは角括弧の中で導入できます。前述のデータ型 List に型パラメータ T を追加して、リストのジェネリック版を作り直すことができます。そうすると、map や reduce のようなリスト向けのジェネリック関数を定義できます。
///|
enum List[T] {
Nil
Cons(T, List[T])
}
///|
fn[S, T] List::map(self : List[S], f : (S) -> T) -> List[T] {
match self {
Nil => Nil
Cons(x, xs) => Cons(f(x), xs.map(f))
}
}
///|
fn[S, T] List::reduce(self : List[S], op : (T, S) -> T, init : T) -> T {
match self {
Nil => init
Cons(x, xs) => xs.reduce(op, op(init, x))
}
}
特殊構文#
パイプライン#
MoonBit には便利なパイプ構文 x |> f(y) と f <| x があり、通常の関数呼び出しを連結したり、ネストしたビルダー風コードを読みやすくしたりできます:
5 |> ignore // <=> ignore(5)
[] |> Array::push(5) // <=> Array::push([], 5)
1
|> add(5) // <=> add(1, 5)
|> x => { x + 1 }
|> ignore // <=> ignore(add(1, 5) + 1)
MoonBit のコードは data-first スタイルに従っています。つまり、関数は対象を第 1 引数に置きます。そのため、パイプ演算子は既定で左辺の値を右辺の関数呼び出しの第 1 引数に差し込みます。たとえば、x |> f(y) は f(x, y) と等価です。
_ 演算子を使うと、x |> f(y, _) のように f の任意の引数位置に x を差し込めます。これは f(y, x) と等価です。ラベル付き引数も使えます。
パイプ演算子はアロー関数にもつなげられます。アロー関数にパイプするときは、関数本体を中括弧で囲む必要があります。たとえば value |> x => { x + 1 } です。
逆パイプ演算子は、右辺を左辺の呼び出しの最後の引数として適用します。たとえば、f <| x は f(x) と等価で、f(a, b) <| c は f(a, b, c) と等価です。これは DSL 風のコードで特に有用で、div([text("hello")]) のようなネストした呼び出しを div <| [text <| "hello"] と書けます。
let page = div <| [
text <| "hello",
section("toolbar") <| fn() { [text <| "save", text <| "cancel"] },
]
inspect(
page,
content="div(text(hello), toolbar: div(text(save), text(cancel)))",
)
逆パイプは最後の引数を付け足すので、最後の引数がラムダである関数とも相性がよく、section("toolbar") <| fn () { ... } のような trailing-lambda スタイルを書けます。
カスケード演算子#
カスケード演算子 .. は、同じ値に対する可変操作を連続して行うために使います。構文は次のとおりです:
let arr = []..append([1])
ここでは、x..f() は { x.f(); x } と同じ意味です。
次のような場面を考えてみましょう。write_string、write_char、write_object などのメソッドを持つ StringBuilder 型では、同じ StringBuilder 値に対して一連の操作を行う必要がよくあります:
let builder = StringBuilder::new()
builder.write_char('a')
builder.write_char('a')
builder.write_object(1001)
builder.write_string("abcdef")
let result = builder.to_string()
builder を何度もタイピングしないようにするため、そのメソッドはしばしば self 自身を返すように設計され、. 演算子で操作を連結できます。immutable な操作と mutable な操作を区別するために、MoonBit では Unit を返すすべてのメソッドに対して、戻り値型を変更せずに連続した操作を行うためにカスケード演算子を使えます。
let result = StringBuilder::new()
..write_char('a')
..write_char('a')
..write_object(1001)
..write_string("abcdef")
.to_string()
is 式#
is 式は、値が特定のパターンに一致するかどうかを調べます。Bool 値を返し、真偽値が期待される場所ならどこでも使えます。例えば:
fn[T] is_none(x : T?) -> Bool {
x is None
}
fn start_with_lower_letter(s : String) -> Bool {
s is ['a'..='z', ..]
}
is 式で導入されたパターン束縛は、次の文脈で使えます:
真偽値の AND 式(
&&): 左辺の式で導入された束縛は、右辺の式で使えますfn f(x : Int?) -> Bool { x is Some(v) && v >= 0 }
if式の最初の分岐:条件がe1 && e2 && ...という真偽式の列である場合、is式で導入された束縛は、条件がtrueに評価される分岐で使えます。fn g(x : Array[Int?]) -> Unit { if x is [v, .. rest] && v is Some(i) && i is (0..=10) { debug(v) println(i) debug(rest) } }
guard条件の後続の文では:fn h(x : Int?) -> Unit { guard x is Some(v) println(v) }
whileループ本体では:fn i(x : Int?) -> Unit { let mut m = x while m is Some(v) { println(v) m = None } }
is 式には単純なパターンしか指定できないことに注意してください。パターンを変数に束縛するために as を使いたい場合は、括弧を追加する必要があります。例えば:
fn j(x : Int) -> Int? {
Some(x)
}
fn init {
guard j(42) is (Some(a) as b)
println(a)
debug(b)
}
正規表現リテラル式#
re"..." は正規表現リテラル式です。型は Regex です。
正規表現リテラルは通常の式なので、ローカル束縛に保存したり、引数として渡したり、デフォルト引数として使ったり、定数として定義したりできます:
let r : Regex = re"a(b+)"
const IDENT_START : Regex = re"[A-Za-z_]"
const IDENT : Regex = IDENT_START + re"[A-Za-z0-9_]*"
Regex 値は + で連接、| で選択を表すように組み合わせることもできます。=~ のように正規表現の定数式が必要な場所では、正規表現リテラルから定義した名前付き const を直接参照できます。
通常の文字列リテラルとは異なり、正規表現リテラルではバックスラッシュを二重にエスケープする必要はありません。例えば re"/\\*" ではなく re"/\*" と書きます。
const REGEX_IDENT_START = re"[A-Za-z_]"
const REGEX_IDENT_CONT = re"[A-Za-z0-9_]*"
const REGEX_AB : Regex = re"a" + re"b"
fn regex_default_arg(re? : Regex = re"abc") -> Bool {
re.execute("zabc") is Some(_)
}
test {
let regex : Regex = re"a(b+)"
assert_true(regex.execute("abbb") is Some(_))
assert_true(regex.execute("ac") is None)
assert_true(REGEX_AB.execute("ab") is Some(_))
assert_true(REGEX_AB.execute("ac") is None)
assert_true(regex_default_arg())
}
不正な正規表現リテラルはコンパイル時に拒否されます。
正規表現リテラルでは MoonBit の正規表現構文を使います。サポートされる形式は次のとおりです:
リテラル文字:通常の文字はそのまま自分自身にマッチします
ワイルドカード:
.は改行を含む任意の 1 文字にマッチします文字クラス:
[abc]、[^abc]、[a-z]文字クラス内の POSIX クラス:
[[:digit:]]、[[:alpha:]]、[[:space:]]、[[:word:]]、[[:xdigit:]]など量指定子:
*、+、?、{n}、{n,}、{n,m}非貪欲量指定子:
*?、+?、??、{n}?、{n,}?、{n,m}?グループ化と選択:
( ... )、(?: ... )、(?<name> ... )、a|bアサーション:
^、$、\b、\Bスコープ付き修飾子:
(?i: ... )による大文字小文字を区別しないマッチ
エスケープは文字列ではなく正規表現の規則で解釈されます。一般的なものには \n、\r、\t、\f、\v、\. や \( のようなメタ文字のエスケープ、そして Unicode エスケープ \uXXXX / \u{X...} があります。リテラルの { にマッチさせたい場合は、\{ ではなく [{] を使ってください。これは、将来 regex literal で補間をサポートするときの構文の余地を残すためです。\{ は補間構文と衝突するためです。
重要な意味論と制限がいくつかあります:
^と$はマルチラインではないアンカーで、入力全体の先頭と末尾にだけマッチします\bと\Bは、正規表現リテラルを一級のRegex値として扱う場合には現在利用できます。ですが、=~のようなregex match expressionの定数コンテキストでは現時点では使えません。この制限は将来緩和される予定です。POSIX 文字クラスは ASCII ベースです
\d、\D、\s、\S、\w、\Wはサポートされません。代わりに[[:digit:]]、[^[:digit:]]、[[:space:]]、[^[:space:]]、[[:word:]]、[^[:word:]]を使ってください。re"..."では\xHHのバイトエスケープは使えません。代わりに Unicode エスケープか通常の文字を使ってください。先読み、後読み、後方参照、文字クラスの集合演算はサポートされません
文字クラスでは
-は範囲を表します。リテラルのハイフンにマッチさせたい場合は\-とエスケープしてください。-を文字クラスの先頭または末尾に置く書き方はサポートされません。
(?<id>[0-9]+) のような名前付きキャプチャグループは Regex 値自身の一部です。Regex::execute や MatchResult::named_group などの API では便利ですが、それ自体が MoonBit の束縛を導入するわけではありません。
正規表現リテラルを一級の Regex 値として使うとき、Regex::execute などの操作は first-match semantics を使います。つまり検索位置から見つかった最初のマッチを返し、longest-match モードは提供しません。
正規表現マッチ式#
正規表現マッチ式では =~ 演算子を使って、regex 定数式で StringView を検索します。これは実験的な lexmatch を置き換えることを意図した新しい正規表現マッチ機能です。式の結果は Bool です。
input =~ re"abc"
input =~ ((PREFIX + SUFFIX) as whole, before=head, after=tail)
input =~ (re"b", before~, after~)
右辺は正規表現の定数式でなければなりません。re"abc" のような正規表現リテラル、名前付き const、あるいは定数を + (連接)、|(選択)、括弧で組み立てた式が使えます。任意の実行時値は使えません。
マッチした部分文字列を束縛するには as を使います。未マッチの前置部分と後置部分を StringView として束縛するには before と after を使います。before~ と after~ は、それぞれ before と after という名前の変数を束縛する短縮形です。
これは正規表現の名前付きキャプチャグループとは別物です。例えば re"(?<id>[0-9]+)" では、id という名前は正規表現エンジンのキャプチャメタデータの一部であり、MoonBit の束縛ではありません。=~ で束縛が必要な場合は、(re"(?<id>[0-9]+)" as digits) のように as を使ってください。
is と同様に、=~ によって導入された束縛は && の右辺や if の true 分岐など、同じブールフローの文脈で使えます。正規表現マッチはデフォルトで検索ベースなので、"zabc!" =~ re"abc" は true です。入力の先頭または末尾にマッチを制限したい場合は、^ や $ のようなアンカーを使ってください。
=~ も first-match semantics を使います。設計上、longest-match の挙動はサポートしません。
test {
let input = " let_name = 42 "
if (input =~ (
(REGEX_IDENT_START + REGEX_IDENT_CONT) as ident,
before=head,
after=tail
)) {
assert_true(head is " ")
assert_true(ident is "let_name")
assert_true(tail is " = 42 ")
} else {
fail("expected identifier")
}
if ("abc" =~ (re"b", before~, after~)) {
assert_true(before is "a")
assert_true(after is "c")
} else {
fail("expected middle match")
}
let source : StringView = "abc"
if (source =~ (re"." as ch, after=rest)) {
assert_eq(ch, 'a')
assert_true(rest is "bc")
} else {
fail("expected leading char")
}
assert_true("zabc!" =~ re"abc")
assert_true(!("zabc!" =~ re"^abc"))
}
上の例では、head、ident、tail、before、after、rest の型はすべて StringView です。束縛 ch の型が Char になるのは、re"." がちょうど 1 文字にマッチするからです。
lexmatch#
警告
lexmatch と lexmatch? は非推奨です。新しいコードでは regex match expression を使ってください。この節は既存コードの参考のためだけに残しています。
lexmatch は String を正規表現パターンと照合し、マッチした部分を束縛できます。search モードのパターンは (before, regex pieces, after) で、before と after は未一致の前後部分に対する任意の束縛です。これらはカンマで区切られます。中央の regex 部分は空白のみで区切ります。regex 自体は文字列リテラルの列として書くので、複数行に分けたり、途中にコメントを挟んだりできます。("b*" as b) のように、マッチしたサブパターンに as で束縛することもできます。
lexmatch? は is に似た真偽チェックで、is 式と同じ文脈で使える束縛を導入できます。
古いコードでは、search モードの lexmatch は次のように書かれていました:
lexmatch text {
(before, "a" ("b*" as b) "c", after) => ...
_ => ...
}
if text lexmatch? ("a" ("b*" as b) "c") && b.length() > 0 {
...
}
新しいコードでは、このような search モードのチェックは =~ で書いてください。
lexmatch には lexer 風のモードもあります。lexmatch <expr> with longest は候補の中から最長一致を選びます(たとえば if|[a-z]* は longest モードでは iff を iff として一致させますが、first-match の search モードではまず if が一致します)。正規表現マッチ式はこの longest-match モードを提供しません。
正規表現リテラルでは \\b と \\B を正規表現構文の一部として使えますが、これらの単語境界アサーションは現在のところ regex match expression の定数コンテキストでは使えません。正規表現を一級の Regex 値として使う場合には動作し、この制限は将来緩和される予定です。また、正規表現リテラルは \\d、\\D、\\s、\\S、\\w、\\W もサポートしません。代わりに文字クラス内で [[:digit:]] のような POSIX 文字クラスを使ってください。
test {
let text = "xxabbbcyy"
if text =~ (re"a" + (re"b*" as b) + re"c", before~, after~) {
inspect(before, content="xx")
inspect(b, content="bbb")
inspect(after, content="yy")
} else {
fail("")
}
if text =~ (re"a" + (re"b*" as b) + re"c") && b.length() > 0 {
inspect(b, content="bbb")
}
let keyword = "iff"
lexmatch keyword with longest {
("if|[a-z]*" as ident) => inspect(ident, content="iff")
_ => fail("")
}
}
スプレッド演算子#
MoonBit には、配列リテラル構文で Array、String、Bytes を構築するときに要素列を展開するためのスプレッド演算子があります。このような列を展開するには先頭に .. を付ける必要があり、対応する要素型を返す iter() メソッドを持っていなければなりません。
たとえば、スプレッド演算子を使って配列を構築できます:
test {
let a1 : Array[Int] = [1, 2, 3]
let a2 : FixedArray[Int] = [4, 5, 6]
let a3 : @list.List[Int] = @list.from_array([7, 8, 9])
let a : Array[Int] = [..a1, ..a2, ..a3, 10]
debug_inspect(a, content="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]")
}
同様に、スプレッド演算子を使って文字列を構築できます:
test {
let s1 : String = "Hello"
let s2 : StringView = "World".view()
let s3 : Array[Char] = [..s1, ' ', ..s2, '!']
let s : String = [..s1, ' ', ..s2, '!', ..s3]
inspect(s, content="Hello World!Hello World!")
}
最後の例は、スプレッド演算子で bytes 列を構築できることを示しています。
test {
let b1 : Bytes = b"hello"
let b2 : BytesView = b1[1:4]
let b : Bytes = [..b1, ..b2, 10]
inspect(
b,
content=(
#|b"helloell\x0a"
),
)
}
TODO 構文#
todo 構文(...)は、まだ実装されていないコードや将来の機能のプレースホルダを示すための特別な構文です。例えば:
fn todo_in_func() -> Int {
...
}