Mere: 言語を作る

リージョン/ビュー型のメモリモデル、能力受け渡し方式のエフェクト、ブラウザ上でのセルフホスト、4 つのバックエンド(インタプリタ / C / LLVM / Wasm)へのコード生成、型安全な並行処理 —— 小さな ML 系言語 Mere の設計日記。完成品の紹介ではなく、意思決定の記録:何を、どう作り、どんな課題が出て、どう解いたか。

mereprogramming-languagecompilerlanguage-designtype-system

この連載について

Mere は小さな ML 系プログラミング言語です —— Hindley–Milner 型推論、代数的 データ型、パターンマッチ。それを積み上げて、最終的には自分自身をブラウザ上で 型検査・評価・コンパイルでき、4 つのバックエンド(ツリーウォーキング インタプリタ、 C、LLVM IR、WebAssembly)へコードを生成し、そのすべてで型安全な並行処理を走らせる ところまで作りました。

この連載は完成した言語のツアーではありません。意思決定の記録です —— 各要素が 何であり、どう作り、どんな課題が出て、どう解決したか。選択が本当の分岐だったときは、 取らなかった選択肢と、選んだ理由も物語の一部にします —— 「これはまだ作らない」 という答えだったときも含めて。

Mere の背後にある考え

ほぼすべての決定を貫く一つの信念があります:

正しさの検証可能性を最適化し、暗黙を明示にする。

具体的には:

  • 暗黙は意図的に取り除く。 実行時の表現や挙動がひそかに変わるものは表に出す —— メモリの所有権、エフェクト、そして(後には)どの値がスレッド境界を越えられるか、 これらすべてが型に見える形になります。
  • 妥協は避けられない。だから「どこで」を選ぶ。 どの設計ページも、妥協が無いふりを せず、自分が行っているトレードオフを名指しします。
  • 記述量は許容する。 コードは AI の支援で書くので、「打鍵量が多い」ことはかつての ほどのコストではありません。おかげで、人間第一の言語なら簡潔さのために隠すところを、 明示的に設計できます。

これは賢さや速さの話ではありません。プログラムを見て、それが何をするか分かるため のものです。

各要素をどう決めたか

プロジェクト全体を貫き、ほぼ毎回登場するパターンがあります:

  1. 紙上 trial —— コミットする前に、設計を紙の上で(そしてしばしば使い捨ての プロトタイプで)考え抜く。
  2. 未決事項を構造化する —— 決めきれない論点は Q-### として立て、意図的に open のままにする。実装が答えを迫った時点で決める。
  3. dogfooding —— 実際のプログラムをツールチェインに通し、設計文書では見えない 問題を炙り出す。

全体のアーク

  • 土台 —— 思想、母言語に OCaml を選ぶこと、動く最小 ML コア。
  • メモリ —— 5 つの戦略を、Trivial / Drop / with を伴う統一された リージョン/ビューモデルへ蒸留する。
  • エフェクト —— 隠れた環境状態ではなく、能力(capability)として受け渡す副作用と、 細分化された借用注釈。
  • コード生成 —— 一つの言語、4 つのバックエンドを歩調をそろえて保つ。
  • セルフホスト —— Mere 自身の字句解析器・構文解析器・整形器・評価器・型検査器・ コード生成器を Mere で書き、ブラウザで動かす。
  • 並行処理 —— spawn / チャネル / par_mapSend / Sync の型安全とともに、 すべてのバックエンドで。
  • これから —— そして同じくらい大事な、意図的に作らなかったもの

どこから読んでも構いませんが、各パートはおおむねこの順で積み上がっています。

公開済みの記事

  1. Mere は何を最適化するか
  2. 母言語を選ぶ
  3. 最小の ML コア
  4. メモリ:5 つの戦略、1 つの選択
  5. 二つの概念が一つだったとき:region と arena
  6. Trivial、そして region が持てない値
  7. view 型:region が易しくする難問
  8. region の中のコレクションと、Trivial 階層
  9. 副作用を値として渡す
  10. 高階関数、エフェクトを漏らさずに
  11. エフェクトの粒度:shared write と Logger 問題
  12. ケイパビリティを束ねる、そしてそれはどこから来るのか
  13. なぜ一つ以上の backend か、そしてそれらを同一に保つとは
  14. インタプリタと C backend:クロージャを手で作る
  15. LLVM backend:コンパイラのための IR へ下ろす
  16. スケールで完全一致を保つ
  17. WebAssembly backend:サンドボックスへコンパイルする
  18. FFI:呼び出すことと、呼び込まれること
  19. docs site を Mere で書く:アプリ規模の dogfood
  20. セルフホスト始動:レキサ、パーサ、そして共有された木
  21. フォーマッタ、そしてセルフホストがセルフホストをパースする
  22. メタ循環評価器:Mere が Mere を走らせる
  23. 型検査器:Hindley–Milner を Mere で書く
  24. コード生成器を Mere で:自分自身を吐くコンパイラ
  25. ブラウザで動く Mere:すべてが収束する場所
  26. 名前「Mere」、そしてそれが何であるか
  27. 公開:使えるものになるという、地味な仕事
  28. 残りを裏切らずに並行処理を設計する
  29. Send と Sync:安全性が乗る述語、そしてその一つの穴
  30. move 追跡:手渡し後の使用を捕まえる
  31. 多相チャネルと Send 束縛
  32. 四つの backend で並行を動かす
  33. par_map:よくある場合を人間的にする
  34. 何かを作らないという決定
  35. 次に作るもの:地図の縁
  36. ほとんどすでにあったパッケージシステム
  37. 違う痛み方をするよう選ばれた二つ目の dogfood
  38. 同じソースを、ホストなしでコンパイルする
  39. trait が与えるものを、trait なしで
  40. backend を正当化する痛みを探して
  41. ドキュメントもまた dogfood だった
  42. 新しい痛みを見つけるにはドメインを変える
  43. 自分自身を一度も走らせたことのないコンパイラ
  44. コンパイラにジャンプを教える
  45. 鏡の中にだけ現れる三つのバグを狩る
  46. バイトごとに
  47. 順序のもう半分
  48. どこで痛むかを選ぶ
  49. 他のプログラムを走らせる
  50. 端末に住む
  51. 自分の出力を読む
  52. バグはブラウザには無かった
  53. 修正が利用者を壊すとき
  54. ワイヤの向こう側になる
  55. 成長は回収ではない
  56. 言語を測る 1 行ループ
  57. interp では動くのに
  58. 型システムが許したレース
  59. 返ってくるメモリ
  60. 単相化こそが辞書
  61. 一万手の探偵
  62. wasm も返す
  63. 既にあった
  64. 日本語を打ってみる
  65. 百万要素
  66. 一つの int、四つの機械
  67. 本物の ALU
  68. バイトが入口を通る
  69. docs を直した絵
  70. 桁であって符号点ではない
  71. 十六進が読めるようになる
  72. 止まれない worker
  73. 諦める supervisor
  74. select は要らなかった
  75. 古典四重奏
  76. 解凍器を書く
  77. 影の中の 3 バグ
  78. bytes ではなかった
  79. 圧縮器を書く
  80. 本物の形式
  81. gzip に並ぶ
  82. records で領域を書く
  83. Mere で Scheme を
  84. 取られていた名前
  85. 六つ目の名前
  86. リストを消す
  87. float を走らせたことのなかった backend
  88. 64 メガバイトの崖
  89. ページの上のパリティ
  90. 千四百八十四の注釈
← Back to Series