mie 標準ライブラリ (prelude) v1
本書は v1 の prelude (起動時に root フレームに束縛される組込群) を定義する。言語の意味論は language-spec.md を参照。本書中の §N 形式の参照は、特に断りがない限り language-spec.md の節を指す。
本書で定義する識別子は 2 語以上の場合 snake_case で命名する (例: to_int, cond_block)。
なお test / assert は prelude には含まれず、標準ライブラリモジュール std:test / std:assert (@import で取得) として提供する。仕様は mie-command.md §7・std/test.md・std/assert.md を参照。
prelude が root に常在束縛する組込は原則 flat な名前 (print / range / if …) だが、namespace object も常在束縛しうる。反射用の reflect (§11) がその初例である (std:assert / std:* は namespace object だが @import 限定で root には常在しない)。
旧 language-spec.md §14 から本書へ分離した経緯のため、節番号の対応は次の通り (Bytes 節 §5 が挿入された結果、以降は 1 つずつ繰り下がる):
| 旧 (language-spec.md) | 新 (本書) |
|---|---|
| §14.1 入出力 | §1 |
| §14.2 Boolean メソッド | §2 |
| §14.3 Integer 演算子・メソッド | §3 |
| §14.4 String メソッド | §4 |
| (新設) Bytes メソッド | §5 |
| §14.5 List メソッド | §6 |
| §14.6 Tuple メソッド | §7 |
| §14.7 制御構造 | §8 |
1. 入出力
各組込 (print / println / eprint / eprintln / input / exit / panic) の形・戻り値・例は自動生成のリファレンス reference/prelude.md を参照。本節はその意味論を定める。
print / println は標準出力、eprint / eprintln は標準エラーへ書き分ける。接尾辞 ln の有無が末尾改行の有無を表す — 素の print / eprint は改行を付けず、println / eprintln は末尾に改行を足す (Rust の print! / println! / eprint! / eprintln!、Haskell の putStr / putStrLn、Scala の print / println と同じ規約)。exit(code) は通常の評価フローを巻き戻してトップで停止し、code をプロセスの exit code にする (回復可能エラーや panic とは別系統で、診断メッセージは出さない)。code が Integer でなければ呼び出し位置の panic (§16)。exit は panic と同じく 発散する (呼び出し位置に値として戻らない) ため、結果型は Never (ボトム型、§12) で任意の位置に置ける (@match { p => v; _ => exit(1) } のような分岐の穴埋めに使える。Never は全型の部分型なので発散アームは分岐合流で他方の具体型へ寄る)。
panic(message) は明示的に バグ層 へ入る入口で、exit と異なり診断付きでトップ停止し非ゼロ終了する (language-spec.md §16.2)。message が String でなければそれ自体が型不一致 panic になる。想定内の失敗には使わない — ファイル不在などの回復可能エラーは値 (Err(e)、§8) で運ぶ。panic はバグの表明専用である。
1.1 input のエラーモデル
input を ! で待つと Result(String) に resolve する。成功時の line は改行を除いた入力 String。失敗時の Err payload は回復可能エラーオブジェクト {kind, message} (§16)。
| ケース | resolve 値 |
|---|---|
| 成功 | Ok(line)。空行は Ok("") |
| 入力終端 | Err({kind := "eof" …}) |
| その他の読取失敗 | Err({kind := "io_error" …}) |
input が返す正規 kind (閉じた一覧):
| kind | 意味 |
|---|---|
eof | 入力が終端に達した (Ctrl-D / リダイレクト末尾 / パイプ閉) |
io_error | その他の読取失敗 |
末尾の入力行に改行が無くても、読めた部分はその行として成功 (Ok(line)) で返り、ゼロバイトで終端したときに eof となる。message は input: ... のように関数名を含む人間可読文字列。分岐は @match res { Ok(line) => …; Err(e) => @match e.kind { … } } で行い、message の文字列マッチに依存しないこと。
2. Boolean メソッド
Boolean のメソッド (not ほか) の網羅表は自動生成のリファレンス reference/prelude.md を参照。
3. 数値 (Integer / Float) 演算子・メソッド
Integer (Int) と Float (Float) は別の数値型で、演算子は実行時型でディスパッチする。暗黙の型変換はない — 混合算術 (1 + 2.0) と混合比較 (1 < 2.0) はエラー / panic で、to_float / to_int で明示変換する (language-spec.md §7.1 / §9.4)。
両者をまとめる型値として Number(= OneOf(Int, Float) の透過的エイリアス、§12)を束縛する。数値多相の関数(std:math の abs / sign 等)は境界付き型変数 a: Number(language-spec.md §17.2)で書く — {x: a: Number, y: a | a} は「両引数が同じ数値型で、結果もその型」を表明し、混在(1 と 2.0)を型エラーにする。単独の注釈 x: Number は「Int または Float」のみで引数間の同型は要求しない。順序演算 min / max / clamp は数値専用でなく Comparable 境界の大域関数(§14.5)。
3.1 Integer
- 算術:
+-*/%(§7.1。Int op Int → Int、/は切り捨て) - 比較:
==!=(§9.1) / 順序<<=>>=(language-spec.md §9.4) - Int は Comparable で
compareを持つ (§14。< <= > >=はcompareから導出) - 文字列との
+連結 (§7.2)
Integer のメソッド (to_char / to_float ほか) の網羅表は自動生成のリファレンス reference/prelude.md を参照。
3.2 Float
- 算術:
+-*/%(§7.1。Float op Float → Float、/は実数除算、%はmath.Mod) - 比較:
==!=(§9.1。構造等価でNaN == NaNはtrue) / 順序<<=>>=(language-spec.md §9.4) - Float は Comparable で
compareを持つ (§14)。順序は 全順序 (NaN にも定位置。Rusttotal_cmp相当)。sortが NaN 混入時も壊れない - NaN / Inf は値として存在しうる (オーバーフロー由来など。language-spec.md §7.1)
Float のメソッド (to_int / is_nan / abs / floor / sqrt ほか) の網羅表は自動生成のリファレンス reference/prelude.md を参照。
注:sin/cos/log/exp/pi等の超越関数は本書のスコープ外 (将来のstd:mathモジュール、ADR 0026)。
4. String メソッド
文字単位は rune (Unicode code point)。"お".length! は 1、"お".0 は "お" (長さ 1 String)。
String のメソッド (length / split / lines / slice / code / each / map / contains / replace / trim ほか) の網羅表は自動生成のリファレンス reference/prelude.md を参照。要素は長さ 1 の String (= 1 rune) で、位置軸 universal read method (each / map / filter / fold / find / any / all / count。language-spec.md §7.4) の map / filter は List を返す (String を返す版は v2 持ち越し)。
String は Comparable で、辞書順 (Unicode code point 単位) の compare / < <= > >= を持つ (language-spec.md §9.4 / §14)。要素を並べ替えるには to_list で List に開いてから sort* する (§6.6)。
5. Bytes メソッド
Bytes は不変のバイト列 (language-spec.md §7.2.2)。s.to_bytes! や std:fs の read_bytes(path) (std/fs.md) で得るほか、Integer の List から構築関数 bytes(list) で作れる (Option(Bytes)、範囲外は None・非 Integer は panic、空は Some(空 Bytes))。range/Range と bytes/Bytes は「リテラルを持たない型を小文字構築関数で作る」対称。
Bytes のメソッド (length / to_string / slice / each / map / contains ほか) の網羅表は自動生成のリファレンス reference/prelude.md を参照。要素は Integer (0〜255) で、map / filter は List を返す (Bytes ではない)。位置軸 universal read method は §6 List と同義。
6. List メソッド
List のメソッド (要素アクセス xs.N、length / push / map / filter / fold / sort / take / flat_map / zip / unique / partition / join / unlines ほか) の網羅表は自動生成のリファレンス reference/prelude.md を参照。本節はその意味論・不変条件を定める。
- 破壊的操作は
@extensible限定:push/popは closed list ではランタイムエラー (language-spec.md §7.4)。 - 非破壊で新 List を返す:
map/filter/sort/sort_by/sort_with/reverse/to_list/take/drop/take_while/drop_while/flat_map/flatten/zip/unique/partitionは元を変えず新 List を返すためclosedlist でも動く。ソートの詳細 (安定性・空 List・key 算出回数・block 制約) は §14.4。 - 位置軸 universal read method:
find/any/all/countはmap/filter/foldと同じく String / Bytes / Range でも動く (§7.4)。take以降は List 固有 (シーケンスはto_listで List に開いてから使う)。 - block 内の制御: 述語・射影
block内のreturnは外側関数から脱出し、break/continueはmap/filterと同じく不可 (エラー)。foldは空ならblockを呼ばずinitを返す。
sort / sort_by / sort_with / reverse / to_list / take / drop / take_while / drop_while / flat_map / flatten / zip / unique / partition は map / filter と同じく非破壊で新 List を返すため、closed list でも動く。ソートの詳細 (安定性・空 List・key 算出回数・block 制約) は §14.4 を参照。
例:
[1, 2, 3, 4].fold(0) { acc, x | acc + x } #> 10 (合計) ["a", "b", "c"].fold("") { acc, s | acc + s } #> "abc" (連結) [].fold(42) { acc, x | acc + x } #> 42 (空は init をそのまま返す)
6.1 名前軸メソッド (universal)
名前スロット軸に対する universal method。位置軸の positions / has_position と対をなす。closed / open を問わず全オブジェクトが持つ。names / values / has_name の形・戻り値は自動生成のリファレンス reference/prelude.md を参照。いずれも名前スロットを 宣言順 で扱う (values は obj.names().map({ k | obj.[k] }) と等価)。
positions / has_position は §3.10 の動的アクセス recv.[Integer] と対応する。names / has_name は recv.[String] の 名前スロット軸のみを担う — recv.[String] はスロットに加えて組込型メソッド・演算子も解決する (§3.2) ため、名前で解決できる全ての列挙は reflect.names / reflect.has_name (§11) が表す。「キー = 名前 (String) | 位置 (Integer)」という二軸の見方は変わらない。
6.5 range メソッド
range(start, end) は prelude 大域。半開区間 [start, end) の整数列を表す遅延シーケンスを返す (language-spec.md §7.6)。start / end は Integer 必須の 2 引数。range(5) は部分適用になる。start / end の受理範囲は実装定義 (int64) であり、超過は実行時エラーになる。
位置軸 universal read method (§6 と同じ length / each / map / filter / fold / find / any / all / count / first / last / positions / has_position および r.N 添字) をすべて実装する。map / filter は List を返す。find は Some(Integer) / None、any / all は Bool、count は Integer。論理添字 i の要素は start + i*step。range 固有メソッド (step / reverse / to_list / contains / index_of) の網羅表は自動生成のリファレンス reference/prelude.md を参照。contains / index_of は実体化せず算術判定する。
例:
range(0, 5).each { i | print(i) } # 0,1,2,3,4 range(0, 10).step(2).to_list() #> [0, 2, 4, 6, 8] range(0, 5).reverse().to_list() #> [4, 3, 2, 1, 0] range(0, 10).map { n | n * n } #> [0,1,4,9,16,25,36,49,64,81] range(1, 6).fold(0) { acc, n | acc + n } #> 15 (左畳み込みで合計) range(10, 0).length() #> 0 (空 range)
6.6 to_list (位置軸 universal: List 化)
to_list は位置軸 universal method で、シーケンスを List に実体化する。positions / each / map などと同じ位置軸の読み取りで、closed / open を問わず動く (§6.1 の名前軸 universal と対をなす)。
| レシーバ | 結果 |
|---|---|
| List | 要素の新 List (コピー) |
| String | 長さ 1 String (rune 単位) の List<String> |
| Bytes | 各バイト (Integer 0〜255) の List<Integer> |
| Range | 全要素を実体化した List<Integer> (§6.5 既存) |
String / Bytes / Range を並べ替えたいときは to_list() で List に開いてから sort* (§6) する ("dcba".to_list().sort().join("") で文字列に戻せる)。
7. Tuple メソッド
Tuple のメソッド (要素アクセス t.N・length) の網羅表は自動生成のリファレンス reference/prelude.md を参照。
8. 制御構造
if / when / while / break / continue / return は予約語ではなく prelude が束縛した特殊オブジェクトである。「特殊」とは実装が組み込み (ホスト言語 Go で書かれている) という意味のみで、評価規則は他の関数と同じ。引数は呼び出し前に評価され、ブロックを渡せばその評価は遅延される。適用規則も関数と同じ — 未束縛スロットが 1 つなら複数引数はタプル 1 個として丸ごと束縛され、2 つ以上なら数の一致が要る(不一致はエラー)、省略された optional 引数はデフォルトで補われる(§3.6)。return / break の多値は return(a, b) / break(a, b) でよい(タプル 1 個の適用)。
when のような conduit 系制御構造(ブロック起動を if 等の builtin に委譲し、自身は return/break/continue を素通しするだけ)は mie でも定義できる — @conduit 注釈(language-spec.md §16.8)を付けた関数は呼び出し境界で制御値を捕捉せず透過するため、when := @conduit {cond, then | if cond then {()}} のように書ける(透過は Go builtin の特権ではない)。本書が builtin として挙げる when 等もこの形で self-host へ移しうる。一方 while のように break/continue を消費するループそのものは別機構(@loop + capture、language-spec.md §16.9)が要り、builtin(native)として提供する。
分岐 dispatch は @match(§8.4)が担う。これは予約語でも prelude 束縛の特殊オブジェクトでもなく、@ 組込特別形(§1.4)— 値でなく言語コアの構文である。@match subject { … }(構造照合+述語ガード)と subjectless @match { … }(独立述語の短絡連鎖)の両形を持つ。型テスト matches は据え置きの universal method(全値に生える。§17.4)。
各制御構造 (if / when / while / break / continue / return) の形・例は自動生成のリファレンス reference/prelude.md にもある。以下 §8.1〜§8.7 でそれぞれの意味論を定める。
8.1 if
if := { cond, then, else | ... } # cond/then/else すべて必須
呼び出し形:
if cond { then_block } { else_block } # 並置 + 短縮ブロック (段階 curry。推奨) if(cond, then_block, else_block) # タプル (cond, then, else) を一括適用
並置形が成立するのは language-spec.md §3.5 の curry 規則による: if x → cond=x で部分適用、(if x) {y} → then={y} でさらに部分適用、(if x {y}) {z} で else={z} を埋めて本体評価。
両形は評価結果が同じだが、並置形が idiomatic。タプル形も有効な構文だが、mie lint の control-juxtaposition ルールが並置形を推奨し、mie lint --fix で並置形へ変換する (mie-command.md §9)。
例:
# 1行: 並置形が基本 result := if (x > 0) { "pos" } { "non-pos" }
条件が中置演算子 (==、<、+ 等) を含む場合は条件を (...) で囲む。 並置 (優先順位 8) が比較・算術 (優先順位 4〜6) より強く結合する設計から来る制約 (language-spec.md §8.3)。if x == 0 {y} {z} のように書くと ((if x) == 0) {y} {z} 方向にパースされ意味が変わる。
複数行でも } と次の { を同一行に置くこと。 then ブロックや else ブロックの中身は自由に複数行に渡って書けるが、then を閉じる } と else を開く { の間に改行を挟むと、その改行が文区切りとして働き、else 側の並置が断ち切られる。
# OK: ` } { ` は同一行 if (x == 0) { print "a" bar() } { print "b" qux() } # NG: `}` の直後で改行すると else 側が独立式扱いになる if (x == 0) { print "a" } { print "b" }
タプル形 if(cond, {...}, {...}) は () の中なので改行が文区切りにならず } と { を別の行に書けるが (language-spec.md §1.3)、mie lint は並置形を推奨し、--fix で } { 同一行の並置形へ変換する。
elseif は else ブロック中で再度 if を呼ぶ:
if (x > 0) { "pos" } { if (x < 0) { "neg" } { "zero" } }
8.2 when
else を省略したいときは when を使う:
when := { cond, then | if cond then { () } { () } }
呼び出し:
when (x == 0) { print "zero" } when (x > 0) { print "positive" } result := when found { value } # 非成立時 result は () (unit)。maybe 値が要るなら if found { Some(value) } { None }
8.3 while
while := { cond_block, body_block | ... }
条件もブロックで渡す (繰り返し評価のため)。
i = 0 while { i < 10 } { print i i = i + 1 }
i は再代入するので = で導入する (mutable local の auto-vivify、language-spec.md §6.1)。i := 0 だと immutable となり i = i + 1 がランタイムエラーになる。戻り値は () (unit)。
8.4 @match
@match subject { arms } # subjectful(構造照合) @match { arms } # subjectless(述語連鎖)
@match は @ 組込特別形(§1.4)で、値ではなく言語コアの分岐構文(universal method ではない。型テスト matches は据え置きの universal method)。subject(対象値)を各 arm の パターンに構造照合し、最初に一致した arm の body(=> の右)を評価してその値を返す。どの arm にも一致しなければ () (unit) を返す。値の等価・型・タプル・レコード・値コンストラクタの形で分岐でき、パターンに述語ガードを重ねられる単一のディスパッチ構造。subject を省いた subjectless 形は独立述語の短絡連鎖を担う(旧 cond を吸収)。
- arm:
{ }内に arm を改行区切りで並べる。各 arm はpattern [| guard] => body形。=>の左がパターン(+任意のガード)、右が分岐先 body。 - OR はカンマ:
pattern1, pattern2 => body。対象がいずれか1つに一致したら body(前から最初の一致で確定)。0, 1, 2 => …はOneOf(0, 1, 2)相当。タプルは括弧(a, b)、OR は括弧なしカンマで区別する。 - ガード
| guard: パターンが一致し束縛が入った後にguardを評価する遅延述語。Bool 厳格(§9.3。非 Bool は panic)。偽なら次の arm へフォールスルーする(body 内ifと違い別 arm へ落ちられるのがガードの意義)。ガード式内で束縛(Some(n)のn等)を参照できる。 - catch-all
_ => body: どの arm にも一致しないとき選ばれる default。_はワイルドカード(専用文法が所有するので[]の値参照にはならない)。最初の 1 つを採用する。 - 型付き束縛
name: T => body: 対象全体を型Tにmatches(language-spec.md §17.4)で照合し、一致したらname := 対象値を body に束縛する。無制約の束縛はname: Any(Anyは全値に matches)。 - パターンの種類(対象全体に照合):
- 型値(
Int/Float/String/Bool/Unit/Any/List(T)/OneOf(A, B)/Option(T)/Result(T)/Future(T)/ shape):matches(language-spec.md §17.4)で照合。束縛しない(型で絞りつつ束縛するなら型付き束縛name: T)。 - タプルパターン
(p₁, …, pₙ): 対象が同 arity n のタプルで、各要素が対応サブパターンpᵢに一致したとき一致(要素ごと再帰照合)。各要素は型値→matches/ 素の値→==/Ctor(…)・レコード→再帰照合。arity 不一致・非タプル対象は(エラーでなく)不一致で次 arm へ。(Int, Int)は「対象がそのタプル型に一致するか」と同結果(要素ごとmatches= タプル型照合の一般化)。 - 素の値(
5/"text"/None/ 計算した変数値):==(language-spec.md §9.1)で照合。ユーザー定義型に==スロットを定義すれば独自の等価性で dispatch できる。束縛しない。 - レコードパターン
{ field… }(末尾パイプ不要 —@matchは専用文法で body(=>の右)と曖昧にならない): 対象の同名スロットを構造照合し、一致したら束縛を body に導入。各 field エントリ: name: 対象が slotnameを持てばname := 対象.nameを束縛(制約なし)。name := v: 対象の slotnameがvと==(§9.1)で一致すれば束縛。name: T: 対象の slotnameが型Tにmatchesすれば束縛。- スロット不在・制約不一致のいずれかでそのパターンは不一致(次の arm へ)。対象が非オブジェクトでもスロット不在で安全に不一致。
- 値コンストラクタパターン
Ctor(pat…)(または無引数Ctor): タグ名で判別し、各 payload 位置をサブパターンpatに再帰照合する。Ctorは bare タグ(destructure 済み、または prelude が先取り公開したSome/Ok/Err等)でも、修飾コンストラクタパターンName.Tag(enum 型値の下のタグを直接参照する形。language-spec.md §17.4)でもよく、両者は同じ被覆・照合規則に従う(Circle(r)≡Shape.Circle(r)、Some(x)≡Option.Some(x))。payload 位置のpatは次のいずれか: - 小文字始まりの bare 識別子 (
x/v/e): その payload を新規束縛する (Some(x)/Ok(v)/Err(e))。_は判別のみで破棄 (Some(_))。 - それ以外 (大文字始まりのタグ
None/Empty/Shape.Empty、入れ子の値コンストラクタCtor(…)、素の値5/"s"、レコード{…}、型値): その payload へのサブパターンとして本リストの規則で照合する (束縛は内側の小文字 bare 識別子だけが行う)。 - 無引数の bare タグ
Circle/ 修飾タグShape.Circle(payload 付きタグ)は payload を問わないタグ照合(束縛しない。c matches Shape.Circleと同義、language-spec.md §17.4)。payload 無しタグ(None/Empty)ではタグ照合と==が一致する。
入れ子は任意の深さで書ける (Ok(Some(x)) / Ok({status := 404}) / Some(Circle(r)))。payload 位置で「大文字始まり=タグ/値照合・小文字始まり=束縛」と分けるのは参照言語 (Haskell / Rust / F#) に倣う (この区別は payload 位置でのみ働く)。実体はタグ付きデータのタグを使ったレコードパターンの糖衣で、ユーザ定義タグ付きデータにも同形で開ける。
- subjectless(
@match { arms }): subject が無いので arm 頭はパターンでなくガード(Bool 式)。上から短絡評価し、最初に真になった arm の body を返す(_は catch-all)。どれも真でなく_も無ければ()。独立述語の順次評価(旧cond)を担う。 - subject の曖昧性:
@matchと{の間に式があれば subjectful、無ければ subjectless。波括弧で始まる/末尾に波括弧を juxtapose する subject は括弧化する(@match ({ … }) { … })。@match {は常に subjectless(Rust の no-struct-literal 規則と同型)。subjectful の arm 頭は常にパターン(Bool 式を書くと==照合、ガードは|の右に置く)。 - 走査は前から順、最初の一致で打ち切り。body は遅延評価(一致した arm だけ評価)。arm body は定義位置のスコープを捕捉する(外側の変数が見える)。型で絞った値全体に名前を付けたいときは型付き束縛
name: T => …を使う。 - body 空の arm は language-spec.md §3.6 ルール5。分岐結果を返したい arm には必ず body を書く。
# 値ディスパッチ(素の値は == で比較。OR はカンマ) sign := { n | @match n { 0 => "zero" "a", "e", "i", "o", "u" => "vowel" # OR _ => if (n > 0) { "pos" } { "neg" } # catch-all } } # 型ディスパッチ + ガード(束縛にさらに述語を課す。偽なら次 arm へ) classify := { v | @match v { Int | v > 100 => "big-int" Int => "int" String => v.length! List(Int) => v.fold(0, { a, b | a + b }) _ => "other" } } # 型付き束縛(型で絞った値全体に名前 n を付ける) classify2 := { v | @match v { n: Int => n + 1 # n := v(Int に絞った値) s: String => s.length! # s := v(String に絞った値) _ => "other" } } # レコード分解 + 束縛(末尾パイプ不要) area := { shape | @match shape { { kind := "circle", r } => 3 * r * r # r を束縛 { kind := "rect", w, h } => w * h # w, h を束縛 _ => 0 } } # subjectless(旧 cond)— 独立述語の短絡連鎖 grade := { n | @match { n >= 90 => "A" n >= 80 => "B" n >= 70 => "C" _ => "F" } }
レコードパターンは { field… } で書く(@match は専用文法なので末尾パイプは不要)。
値コンストラクタパターン(Option / Result)の例:
@match opt { Some(x) => use(x) None => default } @match res { Ok(v) => process(v) Err(e) => handle(e) }
ユーザ定義タグ(@enum。language-spec.md §17.4)も同形で開ける。タグは Shape の下に住むため、修飾コンストラクタパターン Shape.Tag(...) で直接書けるほか、名前 destructure(language-spec.md §6.3)で bare 化すれば従来どおりのパターンで書ける:
@enum Shape := OneOf(Circle(Int), Rect(Int, Int), Empty) @match s { # 修飾コンストラクタパターン(destructure 不要) Shape.Circle(r) => r * r * 3 Shape.Rect(w, h) => w * h Shape.Empty => 0 } [Circle, Rect, Empty] := Shape @match s { # destructure 後は bare パターンも同様に書ける Circle(r) => r * r * 3 Rect(w, h) => w * h Empty => 0 }
payload 位置はサブパターンなので、入れ子の値コンストラクタ・素の値・レコードを任意の深さで照合できる:
# Result(Option(T)) を 1 つの @match で開く(内側 Some は大文字始まりなのでタグ照合、x は束縛) @match res { Ok(Some(x)) => use(x) Ok(None) => empty Err(e) => fail(e) } # payload の素の値・レコードで絞る @match shape { Some(Circle(0)) => "unit circle" # 入れ子の素値サブパターン(== で 0 を照合) Some(Circle(r)) => area(r) Some({ kind := "rect", w, h }) => w * h # 入れ子のレコードパターン _ => 0 }
タプルパターンは複数の値を 1 つの @match で平坦に分岐できる(各要素は素の値なら ==、型値なら matches)。method × path のルーティングを入れ子なしで書ける(subject が ( 始まりなので括弧化は不要):
@match (req.method, req.path) { ("GET", "/") => ok("hello") ("GET", "/todos") => ok(todos) ("POST", "/todos") => created _ => not_found }
8.5 break / continue
ループの反復制御。break は囲っているループを抜け、continue は今の反復を打ち切って次の反復へ進む。
| 構文 | 意味 |
|---|---|
break! (≡ break()) | ループを抜ける。ループ式の戻り値は () |
break value / break(value) | ループを抜け、ループ式全体の戻り値を value にする |
break(a, b)(break((a, b)) も同義) | ループを抜け、戻り値をタプルにする(多値。return と対称) |
continue! (≡ continue()) | 今の反復を打ち切り、次の反復へ進む |
break / continue は予約語ではなく prelude が束縛した値であり、if / while と同じ適用規則で呼ぶ。単独参照 break は値が返るだけで発火しない — 発火には引数なし呼び出しの postfix ! (language-spec.md §3.2、v! ≡ v()) が要る。break value は juxtaposition による適用で、value が中置演算子を含むときは §8.3 の if と同じ括弧制約があり break(x + 1) と囲う。
有効な構文範囲: while と、位置軸の反復メソッド each (String / Bytes / List / Range の s.each(block)、§6〜7) のブロック内でのみ使える。map / filter は結果リストの意味が壊れるため対象外 (使うと panic)。
スコープは字句的 (lexical)。break / continue は それを字句的に囲っているループのブロックにのみ効く。ループ本体から呼び出した別の関数の中で break しても、その関数を字句的に囲うループは無いため panic になる (関数定義境界を越えない)。「呼ばれた文脈のループを遠隔で切る」動的脱出は持たない (C/Java/Python/Rust と同じ。詳細と設計判断は language-spec.md §16.5)。
i = 0 while { i < 10 } { i = i + 1 when (i == 3) { continue! } # 3 だけ print を飛ばす when (i == 7) { break! } # 7 で while を抜ける print i } #> 1 2 4 5 6 # each でも効く (字句的に each のブロック内なので OK) [10, 20, 30].each({ x | when (x == 20) { break! } print x }) #> 10 # 値つき break — ループ式全体がその値になる result := while { true } { when found { break(item) } # while 全体が item を返す }
ループの外 (字句的に囲うループの無い箇所) での break! / continue! は panic (バグ扱い、language-spec.md §16.2)。
8.6 return
関数 (body) から値を返して脱出する。body の任意の位置に書け、末尾の return v は v を置くのと等価 (§11.1)。意義は末尾でない位置から抜けられること (早期脱出の用途)。戻り値は省略可。
| 構文 | 意味 |
|---|---|
return! (≡ return()) | 関数を抜ける。戻り値は () |
return value / return(value) | 関数を抜け、戻り値を value にする |
return(a, b)(return((a, b)) も同義) | 多値タプルで抜ける(タプル 1 個の適用) |
return は予約語ではなく prelude が束縛した値であり、break / if と同じ適用規則で呼ぶ。単独参照 return は値が返るだけで発火しない — 発火には引数なし呼び出しの postfix ! (language-spec.md §3.2、v! ≡ v())、または値の適用が要る。return value は juxtaposition による適用で、value が中置演算子を含むときは §8.3 の if と同じ括弧制約があり return(x + 1) と囲う。多値は return(a, b)(タプル 1 個の適用。return((a, b)) も同義)。, がタプル構築のため括弧なしの return a, b は (return a), b と解釈される。
スコープ: return は最も近い、実呼び出しで入ったユーザ関数 {...} の境界から脱出する。if / when / while / each のブロックと @match の arm body は呼び元 (ビルトインまたは @match 評価器) が呼ぶため透過で、その中の return は外側の関数から脱出する (break が if を貫いてループに効くのと同一)。while / each 本体の return はループでなく外側関数から脱出する (break はループだけ抜ける)。トップレベル (暗黙 root リテラル本体) の return はモジュール / スクリプト評価を終了する。詳細と設計判断は language-spec.md §16.6。
# 逐次の失敗 bail (Result)。`?` 演算子 (language-spec.md §16.7) はこの定型の糖衣で、ここは下地の return を示す process := { path | @match read(path)! { Err(e) => return Err(e) Ok(content) => @match parse(content) { Err(e) => return Err(e) Ok(cfg) => Ok(cfg) } } } # ガード節 (その場で新しいエラーを作って抜ける) classify := { n | when (n < 0) { return "neg" } when (n == 0) { return "zero" } "pos" } # ループ本体の return は外側関数から脱出 (break はループだけ抜ける) find_even := { xs | xs.each({ x | when (x % 2 == 0) { return Some(x) } }) None }
8.7 述語チェーン分岐(subjectless @match)
独立した述語を上から短絡評価する多分岐は、subjectless の @match { … }(§8.4)で書く(旧 cond を吸収。専用構文が導入された 8.4 以前は prelude 束縛の cond が担っていた)。if は else-if を持たず右ネストするため、平坦な述語多分岐はこの形を使う。逆に単一の値の形・型で分岐するなら subjectful の @match subject { … }。
grade := { n |
@match {
n >= 90 => "A"
n >= 80 => "B"
n >= 70 => "C"
_ => "F"
}
}
arm 頭の述語は Bool 厳格(§9.3。非 Bool は panic)、上から最初に真になった arm の body で確定・以降は評価しない(短絡)。body 内の break / continue / return は @match を貫いて外側のループ / 関数に効く(language-spec.md §16.5 / §16.6)。
9. 非同期評価
各組込 (fork / sleep / now / wait_any / all / race) と Future メソッドの形・戻り値は自動生成のリファレンス reference/prelude.md を参照。以下 §9.1〜§9.8 でスケジューラ上の意味論を定める。
Future 自身のメソッド .map / .and_then (合成オペレータ) は §9.6。これらが乗るスケジューラの意味論 (フロー切り替え規則・root 終了時の未解決 Future の扱い・失敗伝播) は language-spec.md §10 を参照。
9.1 fork
fork := { block | ... } # 必須スロット 1 個、組み込み実装
blockは必須スロット 0 個の本体付きオブジェクト ({body}形)- 呼び出し時に block をスケジューラに登録し、即座に
Futureを返す。別フローでの評価は明示的 kick (block!) と同じ意味論 — デフォルトを持つスロットは補完される (language-spec.md §3.6) fork(block)、fork {body}、{body}.fork!は等価 (language-spec.md §8.1)- 引数検証 (fail-fast): 明示的 kick が panic する値 — デフォルトを持たない必須スロットが未束縛で残るオブジェクト (language-spec.md §3.6。本体の有無を問わない) — を渡した場合、
forkはFutureを生成せず呼び出し自身が panic する。評価前に判定できる構造的不整合を別フローの遅延 panic として運ばない (§9.5wait_anyの空 List panic と同種の即時検証)。kick が成功する値 (全デフォルトの block、組込や data object 等の R=0 値) の挙動は変わらない
Future 値の振る舞い:
f!(=f()) で block の評価結果を返す。未完了なら現フローを中断、完了後に値を返す- 同一
Futureへの複数回!は同じ値を返す (block 本体評価は 1 回のみ)。block が panic した場合は panic も memoize され、各!が同じ panic を再現する (language-spec.md §10.2) - 本体付き値なので language-spec.md §3.6 ルール 3 (本体評価) で動く。ルール 5 (本体なしオブジェクトの self 返却) には該当しない
Future同士の==は参照同値 (language-spec.md §9.1)
失敗の扱い: 回復可能エラーは block が Err(e) を 返す 形で表し、input 等と同じく Future が運ぶ通常の Result 値となる (§9.4)。block 評価中の panic (バグ層) は await の ! で取り出した消費側フローへ伝播する (language-spec.md §10.2)。
9.2 sleep
整数 ms ミリ秒だけ現フローを中断する。返り値は () (unit)。現状の v1 では sleep と「未完了 Future への !」だけが中断点 (フロー切り替えが起こりうる点) となる。
9.3 now
現在時刻をミリ秒の整数で返す。中断しない。now! で呼ぶ。
タイミング測定の例:
# (a) 直列 vs 並列 start = now! sleep 100; sleep 100; sleep 100 elapsed_serial := now! - start #> 約 300 start = now! fs := [fork { sleep 100 }, fork { sleep 100 }, fork { sleep 100 }] fs.each({ f | f! }) elapsed_parallel := now! - start #> 約 100 # (b) 別作業との重ね start = now! f := fork { sleep 200; 42 } sleep 100 v := f! total := now! - start #> 約 200 # (c) Result によるエラー (I/O の典型) io_fut := fork { try_io! } @match io_fut! { Ok(v) => use(v); Err(e) => print e.message }
9.4 ブロッキング I/O と Future
ユーザ入力やファイル I/O のような「呼び出し時点で結果が確定しない外部待ち」を行う組込は、即座に Future を返し、! を当てた時点で resolve を待つ形にする。これにより language-spec.md §10.1 の規則 (a) (未完了 Future への !) に自動的に乗り、scheduler token を握りっぱなしにせず他フローが進行できる。
v1 で該当する組込は本書の input (§1)、および標準ライブラリ std:fs の read / read_bytes / write・std:term の read_key (std/index.md) である。後者は prelude ではないが、ブロッキング I/O を Future で表す方針は共通する。これらはいずれも resolve 値を単一 Result に揃えており、成否分岐の書き方 (@match res { Ok(v) => …; Err(e) => … }) が組込をまたいで一致する。
9.5 wait_any
wait_any := { futures | ... } # 必須スロット 1 個、組み込み実装
futuresはFutureの Listsleepと同じく、呼ぶと現フローを中断する組込 (中断点)。最初の入力が解決するまで現フローを中断し、解決した入力Future自身 を直接返す (wait_any自身に!は不要。返るのは値ではなく単層のFuture)- 勝った
Futureの値は通常どおり!で取り出す。勝者は解決済みなので即返る - どの入力が勝ったかは参照同値
==(§9.1) で判別する
win := wait_any([key_fut, tick_fut]) if (win == key_fut) { byte := win!.unwrap! # 勝者から値を取り出す (解決済みなので即返る) } { # tick_fut が勝った }
- 負けた未解決
Futureは変化しない。呼び出し側が参照を保持していれば生存し、後で!/ 再wait_anyできる (持ち越し)。これによりイベントループで「読みかけ」を取りこぼさない
key_fut := read_key()
while { running } {
win := wait_any([key_fut, fork { sleep 16; () }])
if (win == key_fut) {
byte := win!.unwrap!
key_fut = read_key() # 次のキーを張り直す
} {
render() # tick が勝った。key_fut は未解決のまま持ち越し
}
}
- 単発の timeout は
.timeout(ms)(§9.6) を使う。wait_anyを直接使う形でもwait_any([work, fork { sleep 1000; () }])のようにsleepするFutureと多重待ちして合成できる
エッジ:
- 入力が空 List のときは panic (永久ブロックさせない)
- 要素が
Futureでないときは panic - 呼び出し時点で複数の入力が既に解決済みなら、入力 List の 最小位置 の
Futureを返す (決定的)。同一Futureを複数位置に置いた場合も最小位置 wait_any自身は負けフローを停止しないが、負けたFutureを保持していれば.cancel!(§9.8) で停止を要求できる
9.6 合成オペレータ (.map / .and_then / .timeout / all / race)
Future を組み合わせる合成オペレータ。いずれも fork / wait_any / ! の上の糖衣として定義され、失敗伝播は §9.1・language-spec.md §10.2 をそのまま継承する。中で ! を使うため、要素が panic していれば合成結果の Future を await した時点で surface し、要素が Err(e) (回復可能) なら通常の値として block / List に渡る。fork と同じく結果の Future は root 完了時のキャンセル対象に登録される (§13.3)。
Future のメソッド (レシーバ f は Future):
f.map { v | g(v) } # f の解決値 v に g を適用した新 Future。≡ fork { g(f!) } f.and_then { v | fut } # block が Future を返す flatMap。入れ子を平坦化。≡ fork { g(f!)! } f.timeout(ms) # ms 内に解決すれば Some(v)、超過すれば None を運ぶ Future
.map:fの解決値に block を適用する (functor map)。fがErr(e)に解決していれば block はErr(e)をvとして受ける —FutureはResultではないため、Result の.map(Ok の payload だけを写す) とは異なり解決値そのものを写す。Ok だけに作用させたいときはf.map {r | r.map {v | …}}のように合成する。fが panic していれば block を呼ばず panic を伝播する。.and_then: block はFutureを返す。結果は平坦化されFuture(Future(T))にはならない。block がFuture以外を返した場合は await できず panic (バグ層)。fの panic 時は block を呼ばず伝播する。.timeout(ms): 整数msミリ秒だけfの解決を待つFuture(Option(T))を返す。f.timeout(ms)!で取り出し、ms内に解決すればSome(v)、超過すればNone。fがms内に panic していればSomeで包まず panic をそのまま伝播する (await 点で surface)。タイムアウトしてもf自身は自動では停止されず走り続ける (wait_any/raceの敗者と同じ。止めたいならf.timeout(ms)!がNoneの後にf.cancel!する。§9.8)。wait_any([f, fork { sleep ms; () }])の合成を糖衣化したもの。Result/Optionの.map/.and_then(prelude.md §13) と同じ functor / monad の語彙をFutureでも踏襲する。
prelude 関数 (引数 futures は Future の List):
all([f0, f1, f2]) # 全解決を待ち [v0, v1, v2] を運ぶ Future。≡ fork { futures.map { f | f! } } race([f0, f1]) # 最初に解決した値を運ぶ Future。≡ fork { wait_any(futures)! }
all: 入力順に各Futureを await し、値の List を運ぶFutureを返す。all(fs)!で List を取り出す。要素が panic していれば (入力順で最初のものが) 合成Futureの await 時に surface する。空 List は[]に解決する。race: 最初に解決したFutureの値を運ぶFutureを返す。race(fs)!で値を取り出す。wait_anyが勝者Future自身を返す低レベル primitive (敗者の持ち越し・==判別が要るイベントループ向け) なのに対し、raceは勝者の値を返す高レベル糖衣。空 List はwait_anyと同じく panic する。敗者はwait_any同様に自動では停止されず走り続けるが、そのFutureを保持していれば.cancel!(§9.8) で停止を要求できる。
9.7 状態問い合わせ (is_ready)
f.is_ready! # f が解決済みなら true、未解決なら false
Futureのメソッド。fが解決済みならtrue、未解決ならfalseを返す。- 中断しない (
nowと同様に scheduler token を握ったまま即座に返る)。!(= await) が未完了時に現フローを中断して resolve を待つのに対し、is_readyは待たずに完了状態だけを問い合わせる。ポーリングや「!すると中断するかの事前判定」に使う。 is_readyがtrueを返しても、f!の解決値がOk/Err/ panic のいずれかは別問題 (本メソッドは解決済みか否かのみを見る)。
9.8 キャンセル (cancel)
f.cancel! # f を生んだフローのキャンセルを要求する。返り値 ()
Futureのメソッド。fを生んだ別フローのキャンセルを要求する。返り値は()。root 完了時の一括キャンセル (language-spec.md §13.3) と同じ機構をユーザーが明示起動できるようにしたもの。- 協調スケジューラなのでキャンセルは中断点で観測される (プリエンプティブではない)。観測規則 (未開始フロー・待機中フロー・CPU バウンドの扱い) と診断との関係は language-spec.md §10.3。
- キャンセルされたフローの
Futureは panic で解決し、f!で await するとその panic が surface する (§10.2 の失敗伝播に乗る)。回復可能エラーErr(e)ではない (Result の値ではなくバグ層と同じ経路で運ぶ) ため、fork { 42 }の型はFuture(Int)のまま。通常は cancel-and-forget (キャンセルしたものは!しない) で使う。 - 冪等: 同じ
Futureへの複数回.cancel!、および既に解決済みのFutureへの.cancel!は no-op (解決値はそのまま)。
wait_any / race の敗者など「走り続ける負けフロー」も、その Future を保持していれば .cancel! で停止を要求できる (§9.5・§9.6)。
10. ファイルシステム (→ std/fs.md へ移設)
ファイルシステム組込 (read / write / mkdir ほか) は v1 では prelude に含めず、標準ライブラリモジュール std:fs として提供する。@import "std:fs" で取得する。定義は std/fs.md を参照。
[read, write] := @import "std:fs" content := read("foo.mie")!.unwrap!
11. reflect — 反射的な名前列挙
reflect は prelude 常在の namespace object。@import 不要で root に常在し、names / has_name / type_of の 3 slot を持つ。値に「名前で何が呼べるか」を反射的に列挙し、実行時の型を返す。冒頭の注のとおり、prelude が常在束縛する namespace object はこれが初例。
obj.names (§6.1) が 自身の名前スロットだけを返すのに対し、reflect.names(obj) は その値が名前で解決できる全て (自身のスロット + 種類の組込型メソッド + 演算子) を返す。obj.names = 自分の中身 / reflect.names = 反射的フルビュー、という住み分け。関係は obj.names! ⊆ reflect.names(obj)。
reflect の 3 slot (names / has_name / type_of) の形・戻り値は自動生成のリファレンス reference/prelude.md を参照。以下 §11.1〜§11.4 でそれぞれが返す集合・型と不変条件を定める。
11.1 reflect.names(obj) が返す集合
次の和集合 (重複はスロット優先で 1 回だけ):
- 自身の名前スロット名 (束縛済み・未束縛を問わず。
obj.names!と同一集合) - obj の種類に対する組込型メソッド名 (例: list なら
length/first/last/each/map/filter/fold/find/any/all/count/take/drop/take_while/drop_while/flat_map/flatten/zip/sum/min/max/min_by/max_by/unique/partition/sort/sort_by/sort_with/reverse/to_list/contains/positions/has_position/names/has_name/push/pop) - 演算子メソッド名
+-*/%==!=<<=>>=(§8.1) - universal メソッド名
matches(型テスト。language-spec.md §17.4) /compare(順序比較。§14) /inspect(デバッグ表現。language-spec.md §2.5) /reference_equals(参照同一性。language-spec.md §9.1) /copy(複製。language-spec.md §9.5)。いずれも受け手の型に依らず解決される (compareは呼び出し時に Comparable でなければ panic、copyは Copyable でなければ panic、inspect/reference_equalsは全値で成功)。分岐 dispatch は universal method ではなく@match構文 (§8.4)
順序: 自身の名前スロット (宣言順、obj.names! と同じ) → 残りの組込型メソッド+演算子+universal メソッドを名前の昇順 (UTF-8 バイト順)。
11.2 不変条件
obj.names!⊆reflect.names(obj)reflect.has_name(obj, k)⟺k ∈ reflect.names(obj)⟺obj.[k](Stringk) が「名前不在 (no such slot or method)」以外に解決する (値 or 未束縛)obj.has_name(k)⟹reflect.has_name(obj, k)
これにより §6.1 のズレ — obj.["map"] は解決するのに obj.names! / obj.has_name("map") には出ない — は「names / has_name = 自身のスロット軸、reflect.names / reflect.has_name = 解決可能な全名」として定義どおりに説明される。
11.3 注意点
- 演算子・
matches/compare/inspectは受け手の型に依らず解決される (型チェックは呼び出し時のみ)。したがって全ての値で 11 演算子名とmatches/compare/inspectが一律に並ぶ (reflect.names(true)も+/*/</matches/compare/inspect等を含む)。reflect.names/reflect.has_nameは「呼んでも型エラーにならないか」ではなく「名前が解決可能か」を表す (true * xは呼ぶとエラーだが、名前*は解決可能)。 - 宣言済みで未束縛の名前スロットも含む (
obj.[k]は「未束縛」エラーになるが、名前としては既知)。
11.4 reflect.type_of(v) が返す型
値 v の実行時の型を型値(§12)として返す。構造的・再帰的に最も具体的な型を構築する。
- 原始:
Int/Float/String/Bool/Bytes、空タプルはUnit、n 要素タプルは(type_of e₁, …)。 - list:
List(T)。全要素が同型ならそのT、空・混在はList(Any)。 - object(body なし): 束縛済み名前スロットを宣言順に並べた record 型
{name: type_of(値) … |}。 - callable(関数): 宣言された型を反映した関数型を返す。宣言の無い引数・結果は
Anyに倒す。 - 組込関数: 既知の宣言シグネチャ(型変数を含みうる。§17.2)を持つものはその関数型。例:
type_of(print)は{ Any | Unit }、type_of(if)は{ Bool, {| a }, {| a } | a }。 - ユーザ関数(body あり): スロット型注釈(§17.3)のある引数はその型・無い引数は
Any、body 末尾アスクリプション由来の結果型があればその型・無ければAny。全位置が無注釈なら従来どおり{Any, … | Any}(arity 個のAny)。 Some(v)/NoneはOption(type_of v)/Option(Any)、Ok(v)/Err(e)はResult(type_of v)/Result(Any)、Less/Equal/GreaterはOrdering、ユーザ enum タグはTag(type_of payload…)。Future(Any)(payload は内省しない)。range(遅延シーケンス)はRange。標準 Error 値は名前スロット{kind, message}を持つため上記 object 則で record 型{kind: String, message: String |}になる。- 不透明ハンドル(
std:arrayのArray): 要素型を含むArray(T)を返す(list と対称。全要素同型ならそのT、空・混在・封印後はArray(Any))。Arrayは型注釈では型パラメータを持たない不透明型(std/array.md §3)だが、type_ofは最も具体的な実行時型として要素型まで表す。他の不透明値(std:timeのInstant等、要素を持たない)は宣言型を持たずAny。 - 宣言型を持たない値は
Any: 型を返す組込(型コンストラクタList/Option/OneOf… やreflect.type_of自身。「型値の型」を表す語彙が無い)・型値そのもの・内部 panic 層エラー。
不変条件: 全ての値 v について v matches reflect.type_of(v)(§17.4)は真。names / has_name が「名前で何が呼べるか」を返すのに対し、type_of は「その値がどの型か」を返す。
12. 型語彙 (type vocabulary)
型注釈 (language-spec.md §17「型注釈」) で使う型は prelude 束縛の値(型値)である。型語彙の値・@type 定義・照合は実装済みで、束縛/分解/呼び出し時パラメータ/スロットのデフォルト確定で照合を強制する(型不一致は panic)。さらに mie check / LSP は、実行前に健全な範囲で型不一致を検出する(static-analysis.md §2。誤検出は出さない)。関数型は宣言シグネチャの構造照合(引数数・引数型・body 末尾アスクリプション由来の結果型を型値として突き合わせ・無注釈位置は素通し)を行う(language-spec.md §17.4)。残り(未実装): inner-name/束縛注釈由来の結果型照合・呼出時強制(wrap して呼出ごとに実 arg/結果を検査)、Future(T) payload、inner-name 型の照合。
- 原始:
Int/Float/String/Bool/Unit/Bytes/Any/Never(型値) - 特殊:
Any(トップ型・任意の値に一致)/Never(ボトム型・どの値にも一致しない=v matches Neverは常にfalse。panic/exitなど発散位置の結果型で、全型の部分型として分岐合流で他方の具体型へ寄る。language-spec.md §16.2 / §17.2) - 型コンストラクタ:
List(T)/OneOf(A, B, …)/Option(T)/Result(T)/Future(T)(型値を返す) - 順序:
Ordering(=Less∣Equal∣Greater) - 数値グループ:
Number(=OneOf(Int, Float)の透過的エイリアス。閉じた union。数値多相は境界付き型変数a: Numberで書く — language-spec.md §17.2、§3) - 構造的 interface:
Comparable(={compare |}、§14)/Copyable(={copy |}、§16)。prelude 定義の structural 型(language-spec.md §17.5)で、ユーザーも{メソッド名… |}で定義できる - shape 型はリテラル再利用: record/object
{x: Int |}、function{Int, Int | Int}(body=結果型)、tuple(Int, String) - 組込値型:
Range(§7.6 の遅延シーケンス値の型)/Error(={kind: String, message: String |}の shape エイリアス、ResultのErr(Error)が指す。構築はerror(kind, message)、§13.3) - 型位置の
{}はname: 型=型付きフィールド・bare 小文字=メソッドフィールド・bare 大文字=埋め込み・| 結果型=function 型(language-spec.md §17.5)。[...]は型位置では不可 - 型定義は
@type Name := <型式>(§17.4)。右辺を型コンテキストで評価し型値をNameに束縛する - タグ付きデータ定義は
@enum Name := OneOf(Tag(T…), …)(§17.4)。トップレベルに束縛するのはName(OneOf(...)相当の型値)だけで、各タグの値コンストラクタはフラット束縛せずNameの slot(Name.Tag)として住む。bare 名で使うには名前 destructure[Tag, …] := Name(language-spec.md §6.3)で明示的に引き込む。下記 Option / Result / Ordering はこの enum の組込特殊形であり、ユーザは同形のものを@enumで定義できる — prelude はこの同じ機構でOption/Result/Orderingを先取り destructure してSome/None/Ok/Err/Less/Equal/Greaterを bare 公開しているだけで、組込は特別扱いされない - 値の型テストは universal method
matches(value matches T/value.matches(T))で真偽値化する(language-spec.md §17.4)。Tが型値でなければ panic。束縛は伴わない(束縛は@match§8.4)
型名・値コンストラクタ(Some / None / Ok / Err など)は大文字始まりの慣用(予約語ではない)。casing 慣用の全体は language-spec.md §1.2.1 を参照。
13. Option / Result
不在は Option、失敗は Result で表す(language-spec.md §9.2 / §16)。いずれも prelude のタグ付きデータ値で、値コンストラクタで作り @match の値コンストラクタパターン(§8.4)で消費する。どちらも @enum(language-spec.md §17.4)で書けるタグ付きデータの組込特殊形であり(Option ≅ @enum Option := OneOf(Some(T), None)、Result ≅ @enum Result(a, b) := OneOf(Ok(a), Err(b)))、ユーザは同形のものを自分で定義できる(組込はジェネリックな点と、Result が E を省略でき既定 Error を持つ点だけが特権)。
- Option:
Some(v)(在)またはNone(不在)。型値はOption(T)。first/last/index_of/to_int/to_float/to_stringなどが返す。 - Result:
Ok(v)(成功)またはErr(e)(失敗)。型値はResult(T, E)(E 既定は標準 Error{kind, message}。Result(T)≡Result(T, Error))。エラーを型で区別したいときはResult(T, E)で E に構造化型(@enum等)を据える。input/std:fsのread/writeなどは既定のErrorを返す(Future(Result(T)))。success に値が無い操作はResult(Unit)(Ok(())/Err(e))。
値コンストラクタは、payload を取るタグ (Some / Ok / Err) が 1 引数の関数値、payload 無しのタグ (None) が arity 0 で呼び出し不要のそのまま値(language-spec.md §1.2.1)。
bare と修飾は同格: Option/Result は enum 型値なので、タグは本来 Option.Some / Option.None / Result.Ok / Result.Err のように型値の下に住む(language-spec.md §17.4)。prelude はこれを先取りの名前 destructure([Some, None] := Option 相当。language-spec.md §6.3)で bare 公開しているため、通常のコードは修飾せず Some / None / Ok / Err をそのまま使う。修飾形も等価に使え、@match の値コンストラクタパターン(§8.4)・matches・型注釈のいずれでも Some(x) ≡ Option.Some(x)。
13.1 Option メソッド
Option のメソッド (unwrap / or / map / and_then / ok_or / filter / zip / flatten ほか) の網羅表は自動生成のリファレンス reference/prelude.md を参照。map は Some の内容に block を適用し、and_then は block が返す Option を平坦化する(Some(Some(...)) に二重化しない flatMap)。
13.2 Result メソッド
Result のメソッド (unwrap / unwrap_err / or / map / and_then / map_err / or_else / ok / err ほか) の網羅表は自動生成のリファレンス reference/prelude.md を参照。map / and_then は Ok 側に作用し、map_err / or_else は Err 側に作用する。
Result(T, E) では payload を返す/受けるメソッド(unwrap_err・unwrap_or_else / or_else の block 引数・map_err / err)はエラー型 E の値を扱い、map / and_then / or は E を保つ(既定の Result(T) では E は Error)。map_err は E を別の型へ移す唯一のメソッド。
握り潰しは明示的 unwrap!(Err / None で panic)でしか書けない。逐次 bail は ? 演算子(language-spec.md §16.7)が定型の糖衣で、下地は match / return。Option フィールドを貫く連鎖は and_then で、不在/失敗にデフォルトを与える分岐は or / unwrap_or_else で書き、is_some / is_none ガード+unwrap! の手書きほどきを避ける。
13.3 エラー値の構築 (error)
error(kind, message)(型 { String, String | Error })は標準 Error 値(language-spec.md §16.3 の {kind: String, message: String |}、closed なデータオブジェクト)を構築する。オブジェクトリテラル {kind := "...", message := "..." |} と構造等価で、どちらで組んでもよい。型 Error(§12)↔ 構築関数 error の対応は Bytes/bytes(§5)・Range/range(§6.5)と同じ慣習。返り値は素の Error 値であり、Result に載せるときは Err(error(...)) と包む。引数は 2 つとも必須の String(非 String はエラー。strict モードでは静的に検出される)。
e := error("not_found", "config.mie: no such file") e matches Error # true e == {kind := "not_found", message := "config.mie: no such file" |} # true (構造等価) validate := { n | if (n >= 0) { Ok(n) } { Err(error("negative", "n must be >= 0")) } }
kind の集合の設計(組込 kind は正規一覧・ユーザ定義は開いた String・受け手は default 分岐の慣習)は language-spec.md §16.4。
14. 順序比較 (compare / Ordering / Comparable)
順序の意味論 (全順序・原始型の順序・< <= > >= の導出・異型 panic・overload) は language-spec.md §9.4 を参照。本節は prelude が提供する値とメソッドを定義する。
14.1 compare
a.compare(b) は全順序の正準 universal method で、Ordering を返す(Less=a が前 / Equal=同順位 / Greater=a が後)。原始型 (Int/Float/String/Bool/Unit) は組込で持ち、object は compare スロットで持てる。Float は NaN にも定位置を与える全順序(language-spec.md §9.4)。非 Comparable なレシーバ・異型比較は panic(language-spec.md §9.4 / §16.2)。< <= > >= はこの compare から導出される。
14.2 Ordering
Ordering は payload を持たないタグ付きデータ(Less / Equal / Greater の 3 値。§13 の Option / Result と同じ一族で、@enum Ordering := OneOf(Less, Equal, Greater) 相当の組込)。Less / Equal / Greater は arity 0 の値コンストラクタで、呼び出し不要でそのまま値(language-spec.md §1.2.1)。型値 Ordering で matches / 型注釈に使える(language-spec.md §17.4)。§13 と同じく、これらは Ordering の下に住むタグを prelude が先取り destructure して bare 公開したもので、修飾形 Ordering.Less / Ordering.Equal / Ordering.Greater も等価に使える。
Ordering のメソッド (then / reverse) の形・戻り値は自動生成のリファレンス reference/prelude.md を参照。then は o が Equal のときだけ無引数 block(Ordering を返す)を遅延評価し、多段比較の合成に使う。reverse は Less ⇄ Greater を入れ替え Equal は不変。
@match でも分岐できる:
@match o { Less => …; Equal => …; Greater => … }
多段比較は then で合成する(Equal のとき次のキーへ短絡):
a.k1.compare(b.k1) .then { a.k2.compare(b.k2) } .then { a.k3.compare(b.k3) }
14.3 Comparable
Comparable は「順序を持つ値」を表す型値で、構造的 interface {compare |}(language-spec.md §17.5)として prelude が束縛する(組込の特別型ではない)。v matches Comparable は v が原始型 (Int/Float/String/Bool/Unit)、または callable な compare スロットを持つ object のとき true(language-spec.md §17.4)。ユーザーも @type MyOrd := {compare |} で同値の型を定義できる。sort / sort_by(§6)は要素・キーが Comparable であることを要求する。
14.4 List のソート
sort / sort_by / sort_with(§6 List メソッド)は 安定(同順位=Equal / 同キーの要素は元の順序を保つ)で、新しい List を返す(map / filter と同じく非破壊。closed list でも動く)。空 List はそのまま空、単一要素はそのまま。
sort(): 要素をcompareで昇順に並べる。要素は Comparable かつ相互に比較可能(異型混在は §9.4 のとおり panic)。sort_by {x | key}: 各要素からkeyを 1 回だけ 算出し(副作用も 1 回)、key のcompareで昇順。key は Comparable。sort_with {a, b | ...}:blockが返すOrdering(Less=aが前)で並べる。blockがOrdering以外を返すと panic。- いずれの
block内でもbreak/continueは不可(map/filter/foldと同じくエラー)。
降順は、単一キーなら xs.sort_by {…}.reverse()、per-key で向きが違う多段なら sort_with で compare(...).reverse() を then 合成する:
# 「ピン留め優先 → 更新日時降順」 notes.sort_with { a, b | a.pinned.compare(b.pinned).reverse() .then { a.updated_at.compare(b.updated_at).reverse() } }
シーケンス (String / Bytes / Range) を並べ替えるには to_list (§6.6) で List に開いてから sort* する。
14.5 順序演算 min / max / clamp (大域関数)
2 値・3 値の順序演算 min / max / clamp を 大域関数として束縛する。いずれも compare の全順序(§14.1)だけを使う演算なので、数値専用でなく Comparable な同型の値に効く(String などにも使える)。シグネチャは境界付き型変数(language-spec.md §17.2)で書き、同名 a を各位置で共有するため引数は同じ型でなければならない(混在は §14.1 のとおり異型比較で panic)。
| 名前 | 形 | 型 | 意味 |
|---|---|---|---|
min | min(a, b) | { (a: Comparable), a | a } | a と b の小さい方。同順位は a を返す |
max | max(a, b) | { (a: Comparable), a | a } | a と b の大きい方。同順位は a を返す |
clamp | clamp(x, lo, hi) | { (a: Comparable), a, a | a } | x を [lo, hi] に収める(x < lo なら lo、x > hi なら hi、他は x)。lo > hi はエラー |
min(3, 7) #> 3 max(3.0, 7.0) #> 7.0 min("b", "a") #> "a" (String も Comparable) clamp(10, 0, 5) #> 5 clamp(-2, 0, 5) #> 0 min(1, 2.0) # error: 異型比較(Int と Float)で panic
List 全体の最小 / 最大は xs.min() / xs.max()(§6.6・Option を返す)を使う。2 値専用の min / max は畳み込みでなく直接 2 引数を比較する。数値専用の abs / sign は std:math(std/math.md §3)にある。
15. inspect — デバッグ表現 (universal method)
inspect は matches / compare と同系の universal method。全値に生え、受け手の型に依らず解決される (language-spec.md §2.5)。v.inspect() / v.inspect! は v の人間可読なデバッグ表現を String で返す (REPL のエコー・print の非 String 引数・${obj} 補間が内部で使う表現と同じ)。形の詳細は自動生成のリファレンス reference/prelude.md を参照。
- 構造的・型タグなし:
2→2/2.0→2.0/"hi"→"hi"(引用符つき・エスケープ済み) /[1, "hi"]/(1, 2)/{x=1 |}/Some(1)/None。型は構造で判別する。値の型そのものが要るときはreflect.type_of(v)(§11.4) — 表示と内省は別軸。 - 往復可能: 非表示文字は
\u{…}等でエスケープし (language-spec.md §7.2)、出力はできるだけ mie ソースへ貼り戻せる。 - 上書き: 受け手の
inspectスロットで上書きでき (language-spec.md §8.6)、reflect.names(v)(§11) に全値で現れる。上書きが効くのは明示v.inspect!で、REPL エコー等の自動表示は既定の組込表現を使う。
2.inspect! #> "2" "hi".inspect! #> "\"hi\"" (中身は "hi" の 4 文字) [1, "hi"].inspect! #> "[1, \"hi\"]" Some(1).inspect! #> "Some(1)" "${v.inspect!} : ${reflect.type_of(v)}" # 型も併記したいときは合成する
16. copy — 複製 (universal method)
copy は matches / compare / inspect と同系の universal method。全値に生え、受け手の型に依らず解決される。意味論 (self 再束縛・可変性で決まる深さ・グラフコピー・Copyable) は language-spec.md §9.5 が定義する。v.copy() / v.copy! は独立したコピー、v.copy(overlay) は overlay の名前スロットだけを差し替えた複製 (copy-and-update) を返す。形の詳細は自動生成のリファレンス reference/prelude.md を参照。本節はその意味論を定める。
- 複製であって構築ではない: default 式 (§12) は再評価されず、その副作用も走らない。引数つき初期化や副作用つき構築は factory (本体付き 0 引数ブロック) の担当で、
copyはそれを置き換えない。 - 引数つき複製 (copy-and-update):
v.copy(overlay)は複製と同時に overlay の名前スロットの値だけを差し替える。q := p.copy!; q.x = 10の 2 手がq := p.copy({ x := 10 |})の 1 式に縮む。overlay は既存の object リテラルで渡し (新構文なし)、可変性は受け手側を保つ。詳細は language-spec.md §9.5。 - 上書き: 受け手の
copyスロットで上書きでき (language-spec.md §8.6)、reflect.names(v)(§11) に全値で現れる。深いコピーの強制や不透明リソースの複製はここで定義する。 - Copyable:
copyできる値を表す型値で、構造的 interface{copy |}(language-spec.md §9.5 / §17.5)として prelude が束縛する(組込の特別型ではない)。v matches Copyableは通常値でtrue、意味ある複製を定義できない不透明な可変リソース (Future/ 外部ハンドル等) はfalse。copyを Copyable でない値に呼ぶと panic する。
p := { x = 1, y = 2 |} # 更新したいスロットは = (可変)
q := p.copy!
q.x = 10 # q だけ更新。p.x は 1 のまま (状態は独立)
counter := { self | count = 0; inc := { self.count += 1; self.count } |}
counter.inc!
d := counter.copy! # count=1 を引き継ぐ独立カウンタ
d.inc! #> 2 # counter は 1 のまま