OCamlは、母だった?

初めて OCaml に触りました。Julie という、おもちゃの Julia 実装を書くためです。

触る前は、GC つきで WebAssembly にコンパイルできる、ちょっと変わった関数型言語だと思っていました。触ったあとに残ったのは、そういう機能の話ではありませんでした。OCaml は、新しい言語を生み出す力に、何か特別なものを持っているのかもしれない。そう思うようになりました。

ひとりの思いつきでは、なさそうだった

自分の感想だけでは、心もとない話です。だから、他の人たちが同じことをしていないか、調べてみました。

Rust の最初のコンパイラは、Rust では書かれていませんでした。OCaml でした。Graydon Hoare が Rust を作り始めたとき、自分自身をコンパイルできるようになるまでの何年か、Rust という新しい言語は OCaml の上で育っていました。

WebAssembly の話も、似ています。WebAssembly という仕様そのものの「公式のリファレンス実装」——ある WASM バイナリが本当に仕様どおりかを判定する、いちばん権威のある判定者——は、いまも OCaml で書かれています。ブラウザの実装が正しいかどうかを、最終的に照らし合わせる相手です。

新しい言語を生み出す現場に、OCaml がくり返し居る。これは、ひとりの思いつきではなさそうでした。

同じものを、二つの言語で書いてみた

とはいえ、印象だけでは弱いので、実際に比べてみることにしました。同じ小さな言語処理系——型階層、多重ディスパッチ、struct、ゲームループのデモ——を、OCaml と JavaScript の両方で、同じマイルストーンまで書いてみたのです。

違いがいちばんはっきり出たのは、値を表示する show() という、地味な関数でした。

JavaScript 版では、switch 文で値の種類ごとに分岐を書きます。数値、文字列、真偽値、構造体、それから、あとから足した Range という値の種類。この Range の分岐を、書き忘れました。

何が起きたか。コンパイルは通ります。警告も出ません。プログラムはしばらく正常に動き、まったく関係なさそうな場所で、こう言って落ちました。

TypeError: Cannot convert undefined or null to object

Range を表示しようとした場所ではなく、その値を受け取った先の、もっと奥のどこかで。JavaScript の switch は、「値の種類はこれで全部か」を知りません。知らないので、抜けがあっても黙っています。抜けの報いは、原因から遠く離れた場所で、あとになってから届きました。

OCaml 版で同じ抜けをやると、コンパイルが通りません。

Warning 8: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched: VRange (_, _, _)

行番号つきで足りない分岐の形まで示して、教えてくれます。実行する前に。

なぜ、これが「母」に効くのか

新しい言語を作るとき、値の種類はどんどん増えていきます。整数、浮動小数点、文字列、真偽値——ここまでは最初から居る。構造体、範囲、クロージャ、複素数——あとから、機能を足すたびに増えていく。

種類が増えるたびに、「これを扱っている場所、全部に手を入れたか?」という問いが、何度も何度も戻ってきます。実際、この Julie という処理系を育てている間、この問いに何十回も当たりました。値の種類を一つ足すたびに、表示・比較・型タグづけ・演算子・struct のフィールド代入——扱っている場所ぜんぶを、思い出さなければいけない。

JavaScript でこれをやると、思い出す責任は、書き手ひとりの記憶力に丸ごとのっています。OCaml でこれをやると、コンパイラが名指しで、抜けを教えてくれます。「新しい言語を生み出す」という作業は、まさにこの「値の種類が増え続ける」という営みそのものです。だから、この性質がいちばん効くのだと思います。

Rust の最初の何年か、WebAssembly のいまの判定者。新しい言語がまだ若くて、値の種類が毎週のように増えている時期を、OCaml がくり返し支えてきたのだとしたら——それは偶然の一致というより、この一つの性質が、ちゃんと選ばれ続けてきた理由なのかもしれません。

母、と言い切るには

もちろん、これだけで「母」と言い切るのは、乱暴だとも思います。OCaml にも、癖はたくさんありました。エラーメッセージは JavaScript よりずっと不親切なことがあるし、let rec ... and ... の相互再帰グループを正しく組むのに、この処理系を育てている間、何度も同じ種類のコンパイルエラーで足を止められました。母は、優しいばかりではありません。

それでも。値の種類を一つ足すたびに、「抜けはないか」と、コンパイラのほうから聞き返してくれる。この一つの性質だけは、新しい言語がまだ揺りかごの中にいる間、ずっと寄り添ってくれるものだと、実感しました。

Rust も、WebAssembly の判定者も、育つ場所として、この寄り添いを選んだのかもしれません。わたしの Julie も、気づけば同じ場所で育っていました。

つづきは、また、そのときに。

コメント

この記事は fediverse に流れています。あなたのアカウントから返信すると、ここに並びます。