仕様をテストスイートで定義する — 「仕様とは roast を通ることである」
Perl 6 は Apocalypse・Exegesis・Synopsis という 3 層の設計文書体系を持っていた。そして最終的に、仕様の正をテストスイート roast へ移した。散文の仕様には解釈が割れる・実装と乖離する・準拠を検査できないという 3 つの問題がある。テストはそれを解く。しかし代わりに手放したものがある — 散文は全部について曖昧に語り、テストは一部について厳密に語る。
前回、自分の言語の話でこう書いて終わった。
オラクル自身が間違っていたら、5 本揃って間違う。
今回はその問いを、Perl 6 がどう扱ったかの話である。 何をもって「Perl 6 として正しい」と言うのか。
3 層の設計文書
第 3 話で、361 本の RFC を Larry Wall が消化し直したところまで書いた。 その出力が Apocalypse である。そして実際には、文書は 3 種類あった。
| 文書 | 書き手 | 役割 | 比喩 |
|---|---|---|---|
| Apocalypse(黙示録) | Larry Wall | 設計判断とその理由。なぜそうするか | 啓示 |
| Exegesis(釈義) | Damian Conway | Apocalypse を例で解説する | 注解 |
| Synopsis(概要) | 複数 | 仕様として使える要約。実装が参照する | 要約 |
命名はいずれも聖書由来で、Perl 文化らしい言葉遊びになっている。
Apocalypse の番号は 『Programming Perl』(ラクダ本)の章番号に対応している。 「ラクダ本の第 N 章にあたる部分を、Perl 6 ではどうするか」という構成だ。
| 番号 | 主題 |
|---|---|
| A1 | The Ugly, the Bad, and the Good — 全体方針 |
| A2 | 基本データ型、シジル、リテラル |
| A3 | 演算子 |
| A4 | 制御構造 |
| A5 | パターンマッチング — 正規表現の再設計。最も影響が大きかった 1 本 |
| A6 | サブルーチン — シグネチャ、多重ディスパッチ |
| A12 | オブジェクト |
全部は書かれていない。 番号が飛んでいるのはそのためだ。
Synopsis が実質の仕様になった
実装者が日々参照したのは、Apocalypse ではなく Synopsis(S01〜S32) だった。
理由ははっきりしている。Apocalypse は「なぜ」を語る散文で、長い。 仕様書として引くには向いていない。 一方 Synopsis は仕様として引ける形に整理されており、しかも更新され続けた。
| 番号 | 主題 |
|---|---|
| S02 | 語彙・データ型・シジル |
| S03 | 演算子(メタ演算子を含む) |
| S05 | 正規表現と grammar |
| S06 | サブルーチンと多重ディスパッチ |
| S12 | オブジェクト |
| S14 | Role と型 |
| S17 | 並行性 |
| S32 | 標準ライブラリ |
ここまでが「散文の仕様」の時代である。
散文の仕様が持つ 3 つの問題
そして、この形には固有の問題があった。
1. 解釈が割れる
実装者ごとに読み方が違うと、実装同士が食い違う。 第 5 話で書いたとおり、この時期の Perl 6 には複数の実装があった。 Pugs があり、Parrot 上のものがあり、後に Niecza(.NET 上)も現れる。
同じ Synopsis を読んで、違う挙動を実装することが起きる。 どちらが正しいかを決める手続きが、散文には無い。
2. 実装と乖離する
文書の更新が止まっても、誰も気づかない。
これは設計文書一般の問題だ。文書は実行されない。 だから間違っていても、古くなっても、赤くならない。 第 5 話で書いた「検証されていない決定が、検証されないまま積み上がる」と同じ構造である。
3. 「準拠している」を検査できない
これが最も実務的な問題だ。
ある実装が「Perl 6 に準拠している」と主張したとき、 その主張を検証する手続きが無い。 散文を読んで、人が判断するしかない。
roast — 仕様をテストへ移す
そこで Perl 6 は、仕様の正を roast というテストスイートに移した。
Repository Of All Spec Tests。 起源は第 5 話で書いたとおり、Audrey Tang が Pugs で始めた 「実装するたびに公式のテストスイートにテストを書く」という運用である。
そして最終的に、こう宣言された。
仕様とは、roast を通ることである。
「6.c である」とは何を意味するか
第 8 話で扱う 2015 年の 6.c リリースは、roast の特定のコミットを凍結する という形で定義された。
つまり「6.c 準拠」とは「6.c 時点の roast を通る」という意味である。
これは驚くほど具体的だ。散文の解釈問題が消える。
- 実装が「6.c 準拠」と主張するとき、その主張は機械的に検証できる
- 新しい版は、新しい roast の凍結点として定義される
- 古い版の挙動は、古い roast が保存されている限り再現できる
そして第 12 話で扱う「6.d が既定、6.e が策定中」という状態も、 roast の凍結点が 2 つあり、3 つ目を作っているという意味になる。
何を得て、何を手放したか
| 得たもの | 手放したもの |
|---|---|
| 準拠が機械的に検査できる | テストに書かれていない挙動は未規定のまま残る |
| 実装と仕様が乖離しない | 「なぜそうなのか」がテストからは読めない |
| 版を凍結できる(6.c / 6.d) | テストが暗黙に実装の都合を固定してしまう危険 |
右列の 1 行目が重要だ。ここに、この連載の 8 つ目の型がある。
散文は全部について曖昧に語り、テストは一部について厳密に語る。
散文の仕様は、言語の全体について何かを言う。ただし曖昧に。 テストの仕様は、書かれた場所については厳密に決める。ただし、書かれていない場所には何も言わない。
これは優劣ではなく、沈黙の形が違うという話だ。
- 散文は「ここは曖昧だ」と分かる形で沈黙する
- テストは「ここはテストが無い」と気づかれない形で沈黙する
だから roast に移した後も、Raku は散文を捨ててはいない。 Synopsis は歴史文書として残り、言語ドキュメント(docs.raku.org)が実用上の説明を担っている。 正が roast に移った、という関係である。
他の言語はどうしているか
この判断は Perl 6 だけのものではない。
| 言語 | 仕様の正 |
|---|---|
| Raku | roast(テストスイート) |
| ECMAScript | ISO/ECMA の散文仕様 + test262(共通テスト) |
| Ruby | CRuby の実装 + ruby/spec |
| C / C++ | ISO 規格(散文) |
| Go | 言語仕様(散文)+ 単一の参照実装 |
ECMAScript が最も近い。 散文仕様と test262 の両方を持ち、 「準拠」は test262 で測られる。
Raku が特異なのは、長く実装が 1 つしかないのに、仕様を実装から分離し続けたことだ。 第 6 話で書いたとおり、Niecza が止まってからの Raku の実用実装は Rakudo だけだった。
普通、実装が 1 つなら「実装が仕様」で済む。Ruby がそうだ(CRuby が事実上の仕様)。 それでも Raku が roast を維持したのは、複数実装があった時代の遺産であり、 同時に単一実装になった後でこそ効く防波堤でもある。
そしてこの節の最後で書くとおり、2026 年にその遺産が効いた。
実装が 1 つのとき、実装のバグは「言語の仕様」として定着しうる。 roast があると、「それはテストに書かれているか」を問える。
前回の宿題に戻る
第 6 話をこう終えた。
オラクル自身が間違っていたら、5 本揃って間違う。
roast はこの問題を半分だけ解く。
解ける部分: 仕様が実装から独立した場所にある。 Rakudo がどう動くかと、Raku がどう動くべきかが、別のリポジトリに分かれている。 実装のバグが自動的に仕様になることはない。
解けない部分: roast に書かれていないことについては、何も言えない。 私の parity 仕掛けで言えば、インタプリタが間違っていて、 かつその挙動を確かめるテストを誰も書いていない領域は、依然として無防備である。
そして Perl 6 自身、この穴を別の方法で埋めていた。 複数の実装があったことだ。 Pugs と Parrot 系と Niecza が同じ roast を通る、 という状況では、片方にしかない挙動は「仕様の穴か、実装のバグか」のどちらかだと分かる。
2 つ目の実装は、1 つ目の実装が気づけない種類のバグを出す。
第 6 話で「2 人目の利用者が来なかった」と書いた。 仕様の検証という観点では、2 つ目の実装が去ったことにも同じ損失がある。
Niecza が止まってから、Raku は 10 年以上、単一実装で roast を維持してきた。 合理的だが、roast がどれだけ仕様を捉えているかを検証する手段は 1 つ減っていた。
そして 2026 年、2 つ目が戻ってきた
この原稿を書いている 2026 年、Rakudo 以外の実装が 2 つ動いている。
| 実装 | 言語 | 方式 | roast(1,464 ファイル中、完全通過) |
|---|---|---|---|
| mutsu | Rust | バイトコード VM | 1,433(97.9%) |
| Raku++(rakupp) | C++17(外部依存なし) | 手書きの字句・構文解析 + 木渡り評価器。ネイティブバイナリへのコンパイルも | 676(46%) |
どちらも roast を分母にして自分を測っている。 「Raku に準拠している」が主張ではなく数字になるのは、 仕様をテストスイートに移したからである。この回の主題が、そのまま効いている。
⚠️ 数字は 2026 年 9 月 18 日に確認したもの。両プロジェクトとも日々動いており、 mutsu の README は「適合度は毎日良くなっている」と明記している。引用するなら測り直すこと。
つまり、この節の懸念は部分的に解消された。 roast がどれだけ仕様を捉えているかを問い直す手段が、10 年ぶりに戻ってきたことになる。
2 つ目の実装は、1 つ目の実装が気づけない種類のバグを出す。
この型が、いま再び使える状態にある。
次回(第 8 話): Christmas。 2015 年 12 月 25 日、発表から 15 年後に最初の安定版が出る。 15 年とは何だったのかを、ここで一度まとめる。