付録E Lune の設計と、これから
本文はずっと「どう書くか」の話でした。この付録は「なぜそう決めたか」の話です。 言語の設計には、たいてい引き受けた不便があります。それを隠さずに置いておくのが、 この本のいちばん最後の仕事です。
E.1 なぜデフォルト遅延なのか
遅延評価は初学者の罠だ、というのは正しい批判です。 いつ計算が走るのか見えないと、 プログラムの動きが読めません。副作用の順番も直感を外れます。教育向けの言語 (Racket、Pyret、Hedy)がすべて正格なのは、偶然ではありません。
それでも Lune はデフォルト遅延を選び、そしてやめないと決めています。理由は 「遅延が便利だから」ではありません。
批判への答えが「隠す」ではなく「見えるようにする」だったからです。
| 隠す道 | Lune が選んだ道 | |
|---|---|---|
| 評価のタイミング | 見えないので考えない | :thunks で状態を、:trace で瞬間を見る |
| 循環した定義 | 固まる(Haskell の <<loop>>) | RUN0005 を即座に、解説付きで報告する |
| 正格にしたいとき | 言語が勝手に決める | strict / ! / seq / force / deepForce で自分が決める |
第4章がまるごとこの答えです。「信じる必要はありません。すべてその場で観察できます」と 書いたのは、可観測性が設計の代償ではなく設計の中身だからです。
ここには狙いがもう一つあります。正格な言語で「評価戦略」を教えるのは難しい —
教える対象がプログラムの外側にあるからです。デフォルト遅延の言語では、評価戦略が
プログラムの内側の観察対象になります。tick() を2回呼んでカウンタが 0 のままなのを
見た瞬間(付録B)、遅延は概念ではなく現象になります。
設計の緊張 — 「初学者に優しい」と「評価戦略を教えられる」は、普通は両立しません。 Lune はこの緊張を、診断と可観測化で解こうとしている言語です。うまく解けているかは、 あなたが第4章を読み終えたときの感触が答えです。詳しい戦略は
documents/STRATEGY.mdの施策 D・E にあります。
E.2 なぜ null と Option の両方があるのか
第5章で Option[T]、第7章で T? を学びました。「ないかもしれない」を表す道具が
2つある、というのは設計としては贅肉に見えます。
そう見えて、どちらも消せませんでした。
Option は消せません。 これは言語機能ではなく、ADT とパターンマッチがあれば
誰でも書ける型です。実際 prelude の中で type Option[T] = | Some(value: T) | None
として定義されているだけで、コンパイラは特別扱いしていません。ADT を持つ言語から
Option を取り上げることはできない —— 禁止しない限り誰かが定義します。
T? も消せません。 Lune の長期的な目標には JVM 上で動き、Java のライブラリを直接
呼ぶことが入っています(documents/LANGUAGE_FUTURE_SPEC.md)。そして Java の参照は
すべて null になり得ます。T? は将来仕様の Java 型対応表で nullable reference
に対応する型で、つまり外の世界と話すための型です。null のない言語は、null のある
世界と話せません。
だから Lune は null を廃止せず、型で飼いならす道を選びました(第7章)。
「null を消した言語」ではなく「null が入りうる場所を型に書かせ、確かめる前に使わせない
言語」です。
役割は自然に分かれます。
T? | Option[T] | |
|---|---|---|
| 出自 | 言語機能(専用構文 ?? ?. と絞り込み) | prelude の ADT |
| 向いている場所 | データの形。レコードのフィールド、関数の引数と戻り値 | リスト処理と高階関数のパイプライン |
| 外との境界 | Java の nullable 参照に対応する | Lune の内側の話 |
実用の目安は第7章 7.7 節にあります。境界では match で詰め替えれば行き来できます。
正直に言うと — 2つあることの代償は、読者が使い分けを覚えなければならない、という 一点です。これは実際に負担です。7.7 節に判断の目安を置いたのは、その負担をせめて 「表を見れば済む」大きさに抑えるためです。
E.3 v0.1 が引き受けた制限
何を作らなかったかは、何を作ったかと同じくらい設計です。使えないものの一覧と、 出る診断は付録A の A.6 にあります。ここではなぜの側を書きます。
凍結したもの — JVM と OO
class / interface / extends、Java ライブラリの直接呼び出し、JVM バイトコード生成。
これらは将来仕様(LANGUAGE_FUTURE_SPEC.md の Phase 2〜4)にありますが、当面凍結
という判断が明示されています(documents/STRATEGY.md §5)。
理由は能力ではなく戦線の選択です。Java 相互運用は「実務で採用してもらう」ための施策で、 そこは勝ち目の薄い戦場です。一方、教育という用途には Python インタプリタの速度で足ります。 だから Lune は「実務言語になる」道を降りて、「母語で教えてくれるコンパイラを持つ 関数型入門言語」に集中しています。
class や new を書くと構文エラーになるのに、キーワードとしては予約されている —
という中途半端な状態は、この凍結の跡です。将来の席は空けたまま、今は座らせていません。
まだ無いもの — try / catch
失敗を例外で飛ばす道具がありません。これは凍結というより順番の話です。第5章の
Result、第7章の T? という「失敗を値として返す」道具を先に据えたので、例外は
それらと役割が重なります。第9章で「回復可能な失敗は値で表してください、raise の出番は
プログラムを止めるのが正しい場面だけです」と書いたのは、この設計の帰結です。
小さく欠けているもの
break / continue(ループ条件に織り込む — 9.3 節)、record update と record pattern、
mutable な record フィールド、ADT のフィールドアクセス、Stream / Map / Set。
文字型もありません(付録A の A.1)。
このあたりは思想ではなく、まだ手が回っていないだけのものが混じっています。付録A の
A.6 が「何が起きるか」の一覧、LANGUAGE_FUTURE_SPEC.md が「どうしたいか」の一覧です。
E.4 処理系を読みたくなったら
Lune の処理系は lune/ 以下の Pure Python、約 7,400 行、外部依存なしです。
1日で読み切れる規模なので、言語の実装を初めて読む題材に向いています。
ソースは検査の順番に並んでいます。付録A の A.1 で見た診断コードの族(LXL → LAY
→ PRS → MOD → TYP → RUN)が、そのままファイルの並びです。
| ファイル | 行数 | 役割 |
|---|---|---|
lexer.py | 294 | 文字列 → トークン列。LXL |
layout.py | 61 | インデント → INDENT/DEDENT。LAY |
tokens.py | 214 | トークン種別とキーワード表 |
parser.py | 763 | トークン列 → AST(Pratt パーサ)。PRS |
nodes.py | 405 | AST の定義。ここを読むと言語の全体像が分かる |
module_loader.py | 188 | import の解決。MOD |
typechecker.py | 1591 | 型検査と網羅性。TYP・REC。最大のファイル |
evaluator.py | 1259 | 遅延評価とサンク。RUN |
diagnostics.py | 124 | 診断の表示形式(caret や hint の組み立て) |
messages.py | 332 | 日英メッセージカタログ |
explanations.py / explanations_ja.py | 530 / 428 | lune explain の教材カタログ |
formatter.py / fixer.py | 490 / 66 | lune fmt / lune fix |
repl.py | 347 | REPL。Playground もこの ReplSession を駆動している |
cli.py | 269 | コマンドライン入口 |
読む順のおすすめ: nodes.py → lexer.py → parser.py → evaluator.py。
最初に AST を見ておくと、あとは全部「AST をどう作るか / どう扱うか」の話になります。
遅延評価に興味があるなら evaluator.py の Thunk と force_value から入るのが最短です。
仕様書の読み方
documents/ に仕様書が並んでいます。LANGUAGE_SPEC.md が v0.1 の正で、そこから
各論に降りる構造です。
| 知りたいこと | 読む文書 |
|---|---|
| v0.1 で何が使えるか | LANGUAGE_SPEC.md(これが正) |
| 表面構文・EBNF | SYNTAX_SPEC.md / LEXER_PARSER_SPEC.md |
| 遅延評価の規則 | LAZY_EVALUATION_SPEC.md |
| 型検査の範囲 | TYPE_CHECKER_SPEC.md / MATCH_EXHAUSTIVENESS_SPEC.md |
| 診断の設計 | ERROR_DIAGNOSTICS_SPEC.md / ERROR_INDEX_JA.md |
| どこへ向かうのか | LANGUAGE_FUTURE_SPEC.md / STRATEGY.md |
仕様書を読むときの注意 — 仕様書は「正」とされていますが、v0.1 の実装が追いついて いない箇所や、逆に実装のほうが正しい箇所があります。この本の付録A を書く途中でも 2件見つかりました(
LXL0004が出せなかった件と、文字型の件)。疑ったら./bin/luneに通して確かめてください。 それが一次情報です。この本の付録は全部 そうやって書きました。
E.5 これから
documents/STRATEGY.md が向かう先を書いています。要約すると、汎用普及は狙わず
「母語で教えてくれるコンパイラを持つ関数型入門言語」に集中し、JVM / OO 路線は凍結する。
デフォルト遅延は隠すべき弱点ではなく、前面に出す差別化点として扱う。
この本を読み終えたあなたにできることが、いくつかあります。
- つまずいた場所を教えてください。 どの診断で詰まったか、どの説明が足りなかったかは、 そのまま言語の改善点です。診断が不親切だったなら、それは言語のバグだと考えています。
- 診断を増やす。 汎用の
RUN0006に集約されている実行時エラーには、専用コードに 昇格させる余地があります(STRATEGY.md施策 E の作業項目)。 - 書いてみる。 v0.1 でも第13章のようなプログラムは書けます。足りない道具に気づいたら、 それが次に作るものです。
部訓をもう一度。必要になるまで、やらない。一度やったことは、忘れない。 遅延評価とメモ化のことですが、言語の作り方の話でもあります。