本文へ移動
mie 仕様
REPL Playground

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)。exitpanic と同じく 発散する (呼び出し位置に値として戻らない) ため、結果型は 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 となる。messageinput: ... のように関数名を含む人間可読文字列。分岐は @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:mathabs / sign 等)は境界付き型変数 a: Number(language-spec.md §17.2)で書く — {x: a: Number, y: a | a} は「両引数が同じ数値型で、結果もその型」を表明し、混在(12.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 == NaNtrue) / 順序 < <= > >= (language-spec.md §9.4)
  • Float は Comparable で compare を持つ (§14)。順序は 全順序 (NaN にも定位置。Rust total_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 / filterList を返す (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:fsread_bytes(path) (std/fs.md) で得るほか、Integer の List から構築関数 bytes(list) で作れる (Option(Bytes)、範囲外は None・非 Integer は panic、空は Some(空 Bytes))。range/Rangebytes/Bytes は「リテラルを持たない型を小文字構築関数で作る」対称。

Bytes のメソッド (length / to_string / slice / each / map / contains ほか) の網羅表は自動生成のリファレンス reference/prelude.md を参照。要素は Integer (0〜255) で、map / filterList を返す (Bytes ではない)。位置軸 universal read method は §6 List と同義。

6. List メソッド

List のメソッド (要素アクセス xs.Nlength / 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 を返すため closed list でも動く。ソートの詳細 (安定性・空 List・key 算出回数・block 制約) は §14.4。
  • 位置軸 universal read method: find / any / all / countmap / filter / fold と同じく String / Bytes / Range でも動く (§7.4)。take 以降は List 固有 (シーケンスは to_list で List に開いてから使う)。
  • block 内の制御: 述語・射影 block 内の return は外側関数から脱出し、break / continuemap / filter と同じく不可 (エラー)。fold は空なら block を呼ばず init を返す。

sort / sort_by / sort_with / reverse / to_list / take / drop / take_while / drop_while / flat_map / flatten / zip / unique / partitionmap / 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 を参照。いずれも名前スロットを 宣言順 で扱う (valuesobj.names().map({ k | obj.[k] }) と等価)。

positions / has_position は §3.10 の動的アクセス recv.[Integer] と対応する。names / has_namerecv.[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 / filterList を返す。findSome(Integer) / Noneany / 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.Nlength) の網羅表は自動生成のリファレンス 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 を消費するループそのものは別機構(@loopcapture、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 lintcontrol-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} { 同一行の並置形へ変換する。

elseifelse ブロック中で再度 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: 対象全体を型 Tmatches(language-spec.md §17.4)で照合し、一致したら name := 対象値 を body に束縛する。無制約の束縛は name: AnyAny は全値に 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: 対象が slot name を持てば name := 対象.name を束縛(制約なし)。
      • name := v: 対象の slot namev==(§9.1)で一致すれば束縛。
      • name: T: 対象の slot name が型 Tmatches すれば束縛。
      • スロット不在・制約不一致のいずれかでそのパターンは不一致(次の 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 vv を置くのと等価 (§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 は外側の関数から脱出する (breakif を貫いてループに効くのと同一)。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。本体の有無を問わない) — を渡した場合、forkFuture を生成せず呼び出し自身が panic する。評価前に判定できる構造的不整合を別フローの遅延 panic として運ばない (§9.5 wait_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:fsread / read_bytes / writestd:termread_key (std/index.md) である。後者は prelude ではないが、ブロッキング I/O を Future で表す方針は共通する。これらはいずれも resolve 値を単一 Result に揃えており、成否分岐の書き方 (@match res { Ok(v) => …; Err(e) => … }) が組込をまたいで一致する。

9.5 wait_any

wait_any := { futures | ... }    # 必須スロット 1 個、組み込み実装
  • futuresFuture の List
  • sleep と同じく、呼ぶと現フローを中断する組込 (中断点)。最初の入力が解決するまで現フローを中断し、解決した入力 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 のメソッド (レシーバ fFuture):

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)。fErr(e) に解決していれば block は Err(e)v として受ける — FutureResult ではないため、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)、超過すれば Nonefms 内に 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 関数 (引数 futuresFuture の 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_readytrue を返しても、f! の解決値が Ok / Err / panic のいずれかは別問題 (本メソッドは解決済みか否かのみを見る)。

9.8 キャンセル (cancel)

f.cancel!    # f を生んだフローのキャンセルを要求する。返り値 ()
  • Future のメソッド。f を生んだ別フローのキャンセルを要求する。返り値は ()。root 完了時の一括キャンセル (language-spec.md §13.3) と同じ機構をユーザーが明示起動できるようにしたもの。
  • 協調スケジューラなのでキャンセルは中断点で観測される (プリエンプティブではない)。観測規則 (未開始フロー・待機中フロー・CPU バウンドの扱い) と診断との関係は language-spec.md §10.3。
  • キャンセルされたフローの Futurepanic で解決し、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] (String k) が「名前不在 (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) / NoneOption(type_of v) / Option(Any)Ok(v) / Err(e)Result(type_of v) / Result(Any)Less / Equal / GreaterOrdering、ユーザ enum タグは Tag(type_of payload…)
  • Future(Any)(payload は内省しない)。range(遅延シーケンス)は Range。標準 Error 値は名前スロット {kind, message} を持つため上記 object 則で record 型 {kind: String, message: String |} になる。
  • 不透明ハンドル(std:arrayArray): 要素型を含む Array(T) を返す(list と対称。全要素同型ならその T、空・混在・封印後は Array(Any))。Array は型注釈では型パラメータを持たない不透明型(std/array.md §3)だが、type_of は最も具体的な実行時型として要素型まで表す。他の不透明値(std:timeInstant 等、要素を持たない)は宣言型を持たず 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 は常に falsepanic/exit など発散位置の結果型で、全型の部分型として分岐合流で他方の具体型へ寄る。language-spec.md §16.2 / §17.2)
  • 型コンストラクタ: List(T) / OneOf(A, B, …) / Option(T) / Result(T) / Future(T)(型値を返す)
  • 順序: Ordering(= LessEqualGreater
  • 数値グループ: 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 エイリアス、ResultErr(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)。トップレベルに束縛するのは NameOneOf(...) 相当の型値)だけで、各タグの値コンストラクタはフラット束縛せず 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 matchesvalue 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:fsread / 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 を参照。mapSome の内容に 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_errunwrap_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 を返す(Lessa が前 / Equal=同順位 / Greatera が後)。原始型 (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)。型値 Orderingmatches / 型注釈に使える(language-spec.md §17.4)。§13 と同じく、これらは Ordering の下に住むタグを prelude が先取り destructure して bare 公開したもので、修飾形 Ordering.Less / Ordering.Equal / Ordering.Greater も等価に使える。

Ordering のメソッド (then / reverse) の形・戻り値は自動生成のリファレンス reference/prelude.md を参照。thenoEqual のときだけ無引数 block(Ordering を返す)を遅延評価し、多段比較の合成に使う。reverseLessGreater を入れ替え 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 Comparablev が原始型 (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}: 各要素から key1 回だけ 算出し(副作用も 1 回)、key の compare で昇順。key は Comparable。
  • sort_with {a, b | ...}: block が返す OrderingLessa が前)で並べる。blockOrdering 以外を返すと panic。
  • いずれの block 内でも break / continue は不可(map / filter / fold と同じくエラー)。

降順は、単一キーなら xs.sort_by {…}.reverse()、per-key で向きが違う多段なら sort_withcompare(...).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)。

名前意味
minmin(a, b){ (a: Comparable), a | a }ab の小さい方。同順位は a を返す
maxmax(a, b){ (a: Comparable), a | a }ab の大きい方。同順位は a を返す
clampclamp(x, lo, hi){ (a: Comparable), a, a | a }x[lo, hi] に収める(x < lo なら lox > 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 / signstd:math(std/math.md §3)にある。

15. inspect — デバッグ表現 (universal method)

inspectmatches / compare と同系の universal method。全値に生え、受け手の型に依らず解決される (language-spec.md §2.5)。v.inspect() / v.inspect!v の人間可読なデバッグ表現を String で返す (REPL のエコー・print の非 String 引数・${obj} 補間が内部で使う表現と同じ)。形の詳細は自動生成のリファレンス reference/prelude.md を参照。

  • 構造的・型タグなし: 22 / 2.02.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)

copymatches / 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 / 外部ハンドル等) は falsecopy を 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 のまま