本文へ移動
Hikari 仕様

言語哲学

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

Hikari(光)という名は、光が粒子性と波動性を併せ持つように、この言語が関数とオブジェクトという二つの見え方を一つの実体に併せ持つことに由来する。

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

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

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

{ slot-list | body }

縦棒 1 本が「スロット宣言」と「本体」を隔てる。どちらの側も独立に空にできるので、綴りは 3 通りある:

add := { x: Int, y: Int | x + y }   # 両方ある       → 関数として呼べる
origin := { x := 0, y := 0 |}       # 本体を空に     → データオブジェクト
greeting := { "hello" }             # スロットを空に → ブロック (引数を取らない関数)
  • スロットがあり本体もある → 関数として呼べる
  • スロットがあり本体を省略した → データオブジェクト。呼ぶと、束縛の済んだ自分自身が返る
  • スロットが無く本体だけある → ブロック。if のような制御構造へ渡すのはこれである
  • いずれも内部的には 「名前付きスロット集合 + 任意の本体」 という同一の概念

つまり「関数」と「オブジェクト」は同じ概念の二つの見え方にすぎない。値の種別は分かれていない — 呼び出しも等価も本体の有無で分岐せず、本体はその値が持つ属性の一つでしかない。呼び名の違いは慣用であって、正体の違いではない。

2. 一つの原則を貫く

2.1 原始型もオブジェクト、演算子もスロット

整数・文字列・真偽値もオブジェクトである。1 + 21 というオブジェクトの + スロットを呼んでいて、1.+ 21.+(2) はその別の記法にすぎない。ユーザー定義型に + を実装するには、+ という名前のスロットを持たせるだけでよい。

2.2 制御構造も値

ifwhenwhile は予約語ではない。prelude が束縛した普通の値であり、ブロック ({...}) を引数に取る関数呼び出しと同じ規則で動く。別の名前に束縛し直すことも、引数として渡すこともできる。

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

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

3. 並置と curry

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

  • f x の並置はそのまま関数適用
  • (x, y) はどこでも常にタプル
  • タプルを関数に渡すと自動 destructure
  • スロットが埋まりきらない値適用は自動的に部分適用 (新しいオブジェクト) になる

引数を 1 つも渡さない評価 f() だけは、値を渡す呼び出しと別に扱う。デフォルト値の補完はこちらでのみ起こる:

f := { x := 1, y := 2 | x + y }

a := f()    # 引数なしの評価: 未束縛スロットにデフォルトを補完して本体評価 → 3
b := f 5    # 値適用だが y が未束縛 → 部分適用 (本体はまだ走らない)
c := b()    # 残りを評価 → 7

デフォルトを持つかどうかで部分適用か本体評価かが変わらないので、呼び出しの読み方が宣言の綴りに依らない

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

タプルの位置づけ: 上の「1 引数 = 1タプル」モデルを文字通り実装するには 1 要素タプル (x,) リテラルが要るが、Hikari はこれを持たない方針を採る。タプルは 多値運搬専用 — 関数への複数引数のパック、多値返却、分解代入 — として割り切り、「タプルを値として 1 スロット関数に渡す」用途はリスト [3, 5] やオブジェクトリテラルで代用する (language-spec.md §3.5, §7.4)。

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

4.1 学ぶ概念が一つに減る

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

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

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

if がただの値であり、+ がただのスロット名であるなら、ユーザーは言語組み込みと同じ手段で同じ表現力の機能を作れる。unlessif と同格の値であって、呼ぶ側から見て両者に格の差は無い。

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

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

5. 系譜の中での位置

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

  • Smalltalk: 「すべてはオブジェクト」「制御構造はブロック引数のメッセージ送信」。ブロック構文 [:x :y | x+y] と Hikari の {x, y | x+y} はほぼ同形である。
  • Self: クラスを捨て、スロットとプロトタイプだけで世界を構成する。self を予約語にしない姿勢も共通する。
  • Haskell: 並置による関数適用、auto-curry、タプル destructure、レキシカルスコープ。失敗を Result、不在を Option で表す代数的データ型の規律も本系譜に連なる ((value, err) ではなくこちらが血統上の自然形)。
  • Rust: 値を何回使えるかを型に載せる規律 (多重度型、language-spec.md §17.8)、_ を破棄子とする destructure、条件を Bool に限る厳格さ。ただし生存期間の注釈は持ち込んでいない。

Hikari を一言で言うなら、Smalltalk の「値の統一」と Self の「クラス廃止」と Haskell の「並置と curry」を、一つのリテラル構文に圧縮した実験言語 である。資源の規律だけを Rust から借りている。

6. 焦点 — 何のための言語か

Hikari はサーバーとツールのための言語である。ただし縛られるのは標準ライブラリだけで、言語そのものは領域を選ばない。

7. 次に読むもの

本書が述べたのは「なぜこの形なのか」だけで、綴りの規定は持たない。

  • language-spec.md — 言語仕様。本書の各節が指している規定の本体である
  • prelude.md — 起動時に束縛される値と型の一覧。if / match / Option の実際の綴りはここにある
  • std/index.md — 標準ライブラリの一覧。import で取れるものはここに並ぶ
  • glossary.md — 用語集