本文へ移動
mie 仕様
REPL Playground

mie の言語哲学

mie は「関数とオブジェクトを単一の表現に統一する」ことを中心に据えた実験的言語である。本書は仕様の詳細ではなく、なぜそのような設計に至ったか、その背後にある一貫した思想を述べる。仕様の詳細は language-spec.md を参照。

1. 中心思想: 関数とオブジェクトは別物ではない

多くの言語では関数とオブジェクトは別の構文・別の意味論を持つ。関数は「呼ぶもの」、オブジェクトは「フィールドを持つもの」として区別され、それぞれに独自のリテラルが用意される。

mie はこの区別を捨てる。どちらも同じ一つの構文で書く:

{ [inner-name|] slot-list | body }
  • スロットがあり本体もある → 関数として呼べる
  • スロットがあり本体がない → データオブジェクト
  • どちらも内部的には 「名前付きスロット集合 + 任意の本体」 という同一の概念

つまり「関数」と「オブジェクト」は同じ概念の二つの見え方にすぎない。本体を評価できるかどうかという動作の違いであって、正体の違いではない。

2. 一つの原則を貫く

中心思想を本気で貫こうとすると、次のものたちも「別物扱い」できなくなる。mie はそれらをすべて同じ枠に押し込んだ:

2.1 原始型もオブジェクト

整数・文字列・真偽値もオブジェクトである。1 + 21 というオブジェクトに + スロットを呼んでいる。Smalltalk 由来の発想だが、mie ではこれが「特例ではない当然」になる。

2.2 演算子もスロット

+==< は特別な構文要素ではなく、ただのスロット名である。1 + 21.+ 21.+(2) はすべて同じ呼び出しを別の記法で書いたもの。ユーザー定義型に + を実装するには、+ という名前のスロットを持たせるだけでよい。

2.3 制御構造も値

ifwhenwhile は予約語ではない。prelude が束縛した特殊オブジェクト (組み込み実装のオブジェクト) であり、ブロック ({...}) を引数に取る普通の関数呼び出しと同じセマンティクスで動く。「特殊」とは実装がホスト言語側にあるという意味だけで、評価規則は他と何ら変わらない。

その帰結として、ユーザーは独自の制御構造を定義できる:

unless := { cond, block |
  if(cond.not(), block, {
    ()
  })
}

2.4 self/this も予約語ではない

自己参照は 内部名 (inner-name) という仕組みで提供する。{self | ... | ...}self は書き手が選んだ名前にすぎず、this/me でも構わない。レキシカルスコープと組み合わさることで、super/parent のような概念も「外側のリテラルに付けた内部名」として自然に表現される。

「親」や「自分」を言語に予約させない — これは「特例を作らない」原則の必然的な帰結である。

2.5 エラーも値

例外機構や大域脱出を特別に設けない。失敗は普通の値として返り、Result 型で運ばれ、@match の値コンストラクタパターン Ok(v) => … / Err(e) => … で分岐される。不在も同様に Option (Some/None) で表す。「想定内の失敗」は値で扱い、「プログラムのバグ」だけが panic として上位へ抜ける (language-spec.md §16)。制御構造・演算子に続き、エラー処理も「値と関数の統一」の射程に入る。Option / Result / Ordering を支えるタグ付きデータ (代数的データ型) の生成は prelude の特権ではなく、@enum Name := OneOf(...) (language-spec.md §17.4) としてユーザに開かれている — 自分の直和型を宣言し、同じ @match の値コンストラクタパターンで分岐できる。タグは型値 Name の下に住み (Color.None)、フラットな大域名前空間を取り合わない — 複数の @enum が同じタグ名 (None 等) を衝突なく持て、prelude の Some/None/Ok/Err/Less/Equal/Greater は「先に destructure 済みの」ユーザ定義タグと同格であって特別扱いではない。

3. もう一つの軸: 並置と curry

関数とオブジェクトの統一に加え、もう一つの設計選択がある。

関数は概念的に常に1引数を取る。 スロット宣言 {x, y | ...} はその1引数 (タプル) を destructure するパターンと解釈する。これにより:

  • f x の並置はそのまま関数適用 (Haskell 流)
  • (x, y) はどこでも常にタプル
  • タプルを関数に渡すと自動 destructure
  • 名前スロットが埋まりきらない値適用は自動的に部分適用 (新しいオブジェクト) になる。デフォルトの補完は明示的 kick (() / !) でのみ起こる

スロット = 引数仕様 = データフィールド という三位一体が、ここでも貫かれている。関数の引数リストとオブジェクトのフィールドリストは同じ概念 — どちらも「名前で値を受け取る場所」だからである。

タプルの位置づけ: 上の「1引数 = 1タプル」モデルを文字通り実装するには 1要素タプル (x,) リテラルが要るが、mie はこれを持たない方針を採る。タプルは 多値運搬専用 — 関数への複数引数のパック、多値返却、分解代入 — として割り切り、「タプルを値として 1スロット関数に渡す」用途はリスト [3, 5]{|...|} リテラルで代用する (language-spec.md §3.5, §7.4)。1要素タプルを導入して概念モデルと実装を厳密一致させる選択肢は検討したうえで採用しなかった。

4. 唯一の意図的な逸脱

これだけの統一を掲げると、どこかで現実と妥協する必要が出てくる。mie はその妥協点を一つだけに絞り、明示する:

短絡評価 && / || だけは言語組み込みの特例とする。

理由は実用上の負担だけである。短絡を関数化するには右辺をブロックで包む必要があり、a.&&({b}) のような記述を強いるのは流石に負担が大きい。「すべてはオブジェクト呼び出し」原則からの唯一の意図的な例外として、これを認める。

例外を一つに絞り、それを仕様の前面で宣言すること自体が、この言語が「特例を許容しない」姿勢を貫いていることの証である。

追補 (Bool 厳格): かつて truthy/falsy (nil / false のみ falsy) という独自の値判定基準を持っていたが、nil 廃止に伴い条件式を Bool 必須に統一した。これは逸脱の追加ではなく、「条件は Bool」という単一基準への収束であり、if (opt) のような暗黙判定の footgun を排する (Rust / Haskell / Swift と同じく truthy/falsy 強制を持たない)。

追補 (破棄子 _): 宣言順束縛の左辺の bare な _ を「何も束縛しないワイルドカード」とする。これは「_ はただの識別子」という一様性を崩す 2 つ目の意図的逸脱だが、崩れを宣言順束縛の左辺だけに局所化している — _ に値を与えた slot・obj._・名前 destructure では _ は通常の識別子のままであり、破棄は「omit できない宣言順束縛」にのみ宿る。多値分解や値コンストラクタパターンの片側破棄 ([Some(_) | …] 等) という実需要 (§2.5 エラーも値) と、destructure / pattern 主流 (Rust / Elixir / Erlang) への一致を、最小の機構 (読みは既存の未定義エラーに委ねる) で得るための妥協である。

残されたもう一つの非対称: 呼び出しの filled 判定 (language-spec.md §3.6) が必須スロットのみを見るため、デフォルト持ち関数は f 3 のような部分引数呼び出しでも即評価される。これは「すべてはオブジェクト呼び出し」原則自体への逸脱ではなく call 規則の内部的非対称であり、OO コンストラクタの記述簡潔性 (make_point 3Point(3, 0) が得られる等) との引き換えで意図的に残している。「filled = 全スロット束縛、() / ! を明示的 kick」へ統一する候補は language-spec-future.md に残してある。

5. なぜこの統一を目指すのか

5.1 学ぶ概念が一つに減る

関数の構文・オブジェクトリテラル・クラス宣言・メソッド構文・演算子オーバーロード・制御構造 — 通常の言語ではこれらが個別の文法と意味論を持つ。mie ではすべてが { slot-list | body } 一つに帰着する。

新しい概念に見えたものが「同じ概念の別の使い方」だと分かるたびに、言語の見通しは良くなる。

5.2 ユーザー定義と組み込みの境界が消える

if がただの値であり、+ がただのスロットであり、self がただの内部名であるなら、ユーザーは言語組み込みと同じ手段で同じ表現力の機能を作れる。unless を定義することと if の実装を眺めることは、本質的に同じ作業になる。

5.3 「正しい使い方」が一つに定まる

ある機能を実装する方法が複数あり、それぞれに微妙なニュアンスの差がある — これは言語設計上の負債である。mie では、同じ呼び出しを表す三つの記法 (1 + 21.+ 21.+(2)) は意味的に完全に等価である。記法は読み手の好みで選ぶもので、意味の差は無い。

6. 統一原則の一覧

実装やレビューで「これはどう振る舞うべきか」を判断するための基準として、本仕様を貫く原則を列挙する:

  1. すべての値は { [inner-name|] slot-list | body } の形を取る (原始型・コレクションを含む)
  2. 関数は概念的に1引数を取る (スロット宣言はその引数を destructure するパターン)
  3. 並置 = 関数適用 (f x は常に apply)
  4. (...) は要素数で意味が変わる (1要素はグルーピング、2要素以上はタプル、0要素は空タプル。関数適用時に自動 destructure、N と要素数不一致ならエラー)
  5. , はリスト的並列性 (スロット列・タプル要素・引数列・多値返却)
  6. ; / 改行はシーケンス (中間値は捨て、最後の値が返り値)
  7. := は immutable 宣言、= は mutable (宣言と再代入を同じ操作に統合。bare 必須スロット・位置スロットは immutable。closed object では slot はリテラル宣言時のみ生まれる。代入族の文の値は ()=unit で、原則 6「最後の値が返り値」を崩さずに「束縛は名前を導入する操作」であることを値でも表す)
  8. メソッド呼び出しは三つの記法で等価 (1 + 21.+ 21.+(2)、識別子も同様)
  9. 予約語を最小化 (self/this/super/parent は予約語ではない、if/when/while も値の名前)。型変数も bare 小文字識別子(既存トークン)として型コンテキストで機能し、新記号・予約語を導入しない。
  10. 唯一の意図的な逸脱は && / || の短絡 (実用上の妥協)
  11. ! は postfix 呼び出しの短縮 (論理否定は .not())
  12. すべての名前スロットが埋まれば本体評価、一つでも欠ければ部分適用。デフォルト補完は明示的 kick (() / !) に固有 (判定は宣言形に依存しない。制御構造もこの規則で if x {y} {z} の段階 curry が成立する)
  13. 真偽値文脈は Bool 必須 (truthy/falsy なし、非 Bool は panic。if (xs) 不可、if (xs.length! > 0) と明示)
  14. トップレベルは暗黙 root リテラルの body (:= は root フレームのローカル束縛として一元化)
  15. @ は組込名前空間 (@extensible{...} / @extensible[...] は後から slot を追加できる shape-extensible リテラル、@import 'x' はモジュール読込、@type Name := <型式> は型定義、@enum Name := OneOf(...) はタグ付きデータ定義 — 生成されるタグは Name の下に住む slot (Name.Tag) で、フラット束縛はしない。@ の後の組込名だけが特別で、無印 {...} / [...] は closed で literal 宣言時のみ slot 生成。slot 値の immutable/mutable とは直交する軸)

7. 系譜の中での位置

完全に同じ思想を持つ既存言語は知る限り無いが、mie は次の三つの系譜が交差する地点に立っている:

  • Smalltalk: 「すべてはオブジェクト」「制御構造はブロック引数のメッセージ送信」。ブロック構文 [:x :y | x+y]mie{x, y | x+y} はほぼ同形である。
  • Self: クラスを捨て、スロットとプロトタイプだけで世界を構成する。self を予約語にしない姿勢も共通する。
  • Haskell: 並置による関数適用、auto-curry、タプル destructure、レキシカルスコープ。失敗を Result、不在を Option で表す代数的データ型の規律も本系譜に連なる ((value, err) ではなくこちらが血統上の自然形)。

mie を一言で言うなら、Smalltalk の「値の統一」と Self の「クラス廃止」と Haskell の「並置と curry」を、一つのリテラル構文に圧縮した実験言語 である。

加えて、Kernel (Scheme の $vau 系) と通じる「特例を許さない」姿勢 — 例外を一つに絞り明示する設計 — を共有する。