ケイパビリティを束ねる、そしてそれはどこから来るのか

明示的なケイパビリティと細粒度の借用モードは、シグネチャを長くする —— 二行の関数に九つの引数。修正は、この Part 全体が戦って得た明示性を買い戻すことなく、表記を圧縮せねばならない。そして下には第二の問いが待つ:ケイパビリティはどこで構築され、破棄されるのか? どちらの答えも、すでに手にある機構を再利用することになる —— 一つは純粋な型レベルの展開、もう一つはメモリモデル自身の `with` と `region` だ。

mereeffectscapabilitieslifecyclelanguage-design

Part III はケイパビリティを値として渡すことでエフェクトを可視にし、高階関数を通しても可視に 保ち、借用モードで粒度を与えてきた。どの一歩も正直さを足し、正直さが長さを足した。この エフェクト最終回は、その勘定を払う話だ —— シグネチャは長くなった —— そして、Part がずっと 暗黙に委ねてきた問いの話だ:ケイパビリティは実のところどこから来るのか?

十三行のシグネチャ

ケイパビリティ渡しと細粒度借用を現実的な関数の上で合わせると、代価は歴然だ:

fn save_order[region A](
    order:   owned Order,
    db:      &mut A Database,
    cache:   &borrowed Cache,
    logger:  &shared write Logger,
    mailer:  &borrowed Mailer,
    metrics: &shared write Metrics,
    clock:   &borrowed Clock,
    rng:     &borrowed Random,
    arena:   &A Region,
) -> Result[owned OrderId, owned DbError]
    where len(order.items) > 0
{
    logger.info("saving order")
    db.insert("orders", order)
}

九つの引数、where 句、戻り値の型 —— 二行の本体に対して十三行のシグネチャ。こういう関数が一つ なら構わない。コードベース丸ごとがこれだと、実際に何かをするコードを飲み込むレビュー負荷に なる。これが、最初のエフェクト回が旗を立てた人間工学の負債で、いま返済期限が来た。

誘惑は、ケイパビリティを隠すことだ —— それら全てをこっそり運ぶ「コンテキスト」オブジェクト —— だがそれこそ Part III が拒んできた手だ。エフェクトを再び不可視に移動させるからだ。最初の 回で述べた区別が、ここでの蝶番のすべてだ:「運搬を楽にしない」は「束ねない」ではない。 束ねることは許される。隠す束ねが許されない。仕事は、情報を消さずに記述を圧縮する束ねを 見つけることだ。

シグネチャエイリアス:情報でなく表記を圧縮する

Mere の答えはシグネチャエイリアス —— その場で展開される、名前付きの引数群だ:

signature server_caps =
    db:      &mut Database,
    cache:   &borrowed Cache,
    logger:  &shared write Logger,
    mailer:  &borrowed Mailer,
    metrics: &shared write Metrics;

fn save_order[region A](order: owned Order, ...server_caps, arena: &A Region)
    -> Result[owned OrderId, owned DbError]
{
    logger.info("saving order")
    db.insert("orders", order)
}

...server_caps の spread が、その地点で五つの引数をすべてシグネチャに落とし込む。隠れた コンテキストオブジェクトが棄却された場所でこれが許されるのは、一つの性質による:それが 参照透明だということだ。spread は純粋な型レベルの置換で —— 機械的に十三行の形へ展開し 戻せて、二つは同じ関数だ。個々の名前は生き残る:dbloggermailer は、ctx. の裏に 埋められず、使うためにそこにある。情報量は同一で、表記だけが短い。

それが、Part 全体が抱えてきた緊張の解消だ。Part III は冗長さを明示性の代価として受け入れた —— だがシグネチャエイリアスは、その二つが実は決して同じものでなかったことを示す。明示は 冗長と同じではない。 あらゆるケイパビリティを名づけられ検査されたまま保ちつつ、なお全ての 箇所でリストを書き出さずに済ませられる —— 略記がちょうど長い形へ展開し、何も隠さない限りは。 コンテキストオブジェクトはその試験に落ちる —— 情報を取り除く —— だからそれは棄却されたまま で、エイリアスは採用された。

縁がないわけではなく、ノートはそれに正直だ:どちらも db を定義する二つのエイリアスを spread すると名前衝突で、それをコンパイルエラーにして明示的な rename で直すことで解決する;そして シグネチャは共有された契約だから、一つを変えるとそれを spread する全てに波及する。検討された 代替 —— Scala 的なモジュールレベルの暗黙引数と、モジュール内でケイパビリティを黙って埋める 推論 —— はどちらも同じ理由で棄却された:それらは、言語が最も明示を望む境界でこそ暗黙性を 再導入することで、簡潔さを買うのだ。

ケイパビリティはどこから来るか —— そして Part II との再会

シグネチャは、関数が Logger受け取ると言う。それは、誰が logger を作ったか、いつ壊される かを言わない。logger は構築されねばならず、データベース接続は開かれ、やがて閉じられねばならない。 それは誰の仕事で、どこに住むのか?

四つの選択肢が机上にあった:全てを main で作って下へ渡す(古典的な依存性注入);ケイパビリ ティをモジュールレベルのグローバルとして構築する(即座に棄却 —— それこそ Part 全体が防ぐために 存在する、環境的で不可視な権限だ);ケイパビリティを region に紐づけて region が消えるとき共に 死なせる;あるいは明示的なスコープで構築し破棄する。Mere は後ろの二つをハイブリッドとして 採り、最初のものをその特殊ケースとして畳み込んだ —— そして際立つのは、それが新しい機構を まったく要さなかったことだ。必要な構文が、Part II ですでに作られていたからだ。

ケイパビリティは with で構築され解放される —— Trivial と片付けのメモリモデル回の、まさに あの構文だ。これは偶然ではなく、言語の二つの半分が出会う地点だ。ほとんどのケイパビリティは Drop を持つ:データベースは接続を閉じ、ファイルは fd を解放し、logger は flush する。それが まさに、Trivial な値しか持てない region に住めない理由であり —— そしてまさに、あらゆる出口 経路で LIFO 順に片付けを管理するために存在する with に住む理由だ。エフェクトシステムの ライフサイクル問題と、メモリモデルの片付け問題は、同じ問題で、with はすでにその答えだった。

これがケイパビリティに、同じ二つの構文を入れ子にして表現される、自然な寿命の階層を与える:

  • アプリ全体 —— プログラムの最外スコープの with、root region の上に、プロセスと同じだけ 生きるもののため:接続プール、logger。
  • リクエスト単位 —— 子 region の上の入れ子の with、リクエスト自身のケイパビリティと スクラッチメモリのため、リクエスト終了時にすべて共に解放される。
  • 関数スコープ —— 短くしか要らないケイパビリティのための局所の with

依存性注入 —— 全てを main で作ること —— は、これの最外の層にすぎず、別の機構ではない。そして テストは、最初のエフェクト回でそうだったように、無料でこぼれ落ちる:テストは外側の with で 偽物を構築し、テスト対象のコードは手渡されたものより先に手を伸ばせない。

Part III、閉じる

エフェクトモデルはいま完成し、その形はメモリモデルのそれと韻を踏む。エフェクトは値であり、 明示的に渡される;高階関数を通しても可視のままだ、ケイパビリティが関数が汎用になる前に消費 されるから;粒度を運ぶ —— 読みか書きか、共有か排他か —— 型で検査され実行時に無料で;隠さずに 束ねられる、正直な長い形へ展開するエイリアスによって;そして with で生まれ死ぬ、メモリの 片付けを管理するのと同じ構文で。ここには環境的なものが何もなく、何も不可視に移動せず —— そして物語るように、最も難しい二つの人間工学の答えは、さらなる発明ではなく、すでに手にある 機構を再利用した。

二つの Part がいまや言語が何であるかを描いた:メモリモデルとエフェクトシステム、どちらも 明示的で、どちらも検査でき、どちらも何度も再登場する小さな概念の集合に寄りかかる。それらが 示していないのはどう走るかだ。言語は、何かがそれを実行するまでは約束だ —— そして Mere は それを四つの異なるやり方で実行し、それらを厳密に一致させることを選んだ。次、Part IV が開く: なぜ言語は一つ以上の backend を要するのか、そしてそれらを同一に保つとは何を意味するのか。

← Back to Mere: 言語を作る