第 5 課:コーエフェクト:計算が環境に求めるもの
一言でいうと:エフェクトは「プログラムが世界に何をしたか」を扱い、コーエフェクトは「世界がプログラムに何への依存を求めるか」を扱います。コーエフェクトが答えるのは「計算が環境に与えてほしいものは何か」(アクセスするリソース、備えるべき能力、依存するサービス)であり、エフェクトとは向きが逆の、互いに双対な概念です。
1. 双対な導入:一方は「私は何を変えたか」を問い、もう一方は「私は何を必要とするか」を問う
まず、2 つの思考実験をしてみましょう。どちらもあなたが毎日経験していることです。
出前を注文する:これは「エフェクト」です。 あなたが「注文する」を押すと、世界は本当に変わります。店が調理を始め、配達員が注文を受け、あなたの財布からお金が減り、道路に配達ボックスが 1 つ増えます。プログラム(この注文という動作)が世界に影響を与え、その影響はプログラムから世界へと流れるのです。
働いて給料をもらう:これは「コーエフェクト」です。 あなたは入社したばかりのプログラマーです。デスク、パソコン、ネットワーク、データベースの権限、プロジェクトのドキュメント……どれ一つとして自分で持ってきたものはなく、すべて会社が提供してくれます。これらがなければ、一行のコードも書けません。給料は言うまでもなく「世界があなたに与えるもの」です。ここでは、環境(会社)がプログラム(あなた)に物を提供しているのであり、向きはちょうど逆です。
この 2 つの例を論文の言葉に翻訳すると、この課の核心になります。
エフェクトはプログラムの世界への影響を特徴づけ、コーエフェクトは世界のプログラムへの制約を特徴づける。
- 出前の注文 = プログラムが世界を変える → エフェクト
- 働くこと = 世界がプログラムにリソース、能力、報酬を提供する → コーエフェクト
では「世界のプログラムへの制約」とは、具体的にどのような姿をしているのでしょうか? 論文は 1 つのリストを示しています。それは計算がその環境に対して要求するものを記述するもので、たとえば次のようなものです。
| 求めるもの | プログラムでの姿 |
|---|---|
| アクセスが必要なリソース | データベース接続、ファイル、メモリ、ネットワーク |
| 備えるべき能力 | ある権限、1 つの鍵、ある API を呼び出す資格 |
| 依存が必要なサービス | 外部システム、別のプラグインが提供する機能 |
型システムの中では、コーエフェクトは次のような姿をしています。通常の判断は Γ ⊢ t : T(コンテキスト Γ の下で、t の型は T)ですが、コーエフェクトシステムでは判断を Γcoeffect ⊢ t : T と書きます。コンテキスト Γ 自体が「コーエフェクト代数」の要素で注釈され、この注釈こそが「この計算が環境に対して要求するもの」なのです。
效应问「我改了什么」(对世界的影响);余效应问「我需要什么」(世界对我的约束)
上の図と照らし合わせて、この「双対」だけ覚えておけば十分です。エフェクトは計算から世界へ向かい(私は何を変えたか)、コーエフェクトは世界から計算へ向かいます(私は何を必要とするか)。一方が豊かにするのは型であり、もう一方が豊かにするのはコンテキストです。
| エフェクト(effect) | コーエフェクト(coeffect) | |
|---|---|---|
| 問うこと | 私は何を変えたか? | 私は何を必要とするか? |
| 向き | 計算 → 世界 | 世界 → 計算 |
| 特徴づけるもの | プログラムの世界への影響 | 世界のプログラムへの制約 |
| 豊かにするもの | 型 | コンテキスト |
| 日常の例 | 出前の注文:注文後に世界が変わる | 働くこと:会社がデスク、権限、給料を提供する |
2. コモナド的コーエフェクト:プログラムは「環境に浸かっている」
前の課ではモナド、「値を箱に入れる」デザインパターンを学びました。この課の双対はコモナド(comonad)です。モナドは副作用を箱に入れますが、コモナドはプログラムはそもそもコンテキスト(環境)に浸かっていると想定し、どのステップでも環境から物を「取り出す」ことができます。
論文の中で最も直観的な例が環境コモナドで、次のように書きます。
D(X) = E × X
この式を怖がる必要はありません。言っているのは、すべての計算 X は、環境 E を携えて一緒に走るということです。スマートフォンの App が常に「現在時刻」と「現在地」という 2 つの環境の値を持ち歩き、どのステップでも読み取れるのと同じです。
コモナドには 2 つの基本操作があり、直観的には次の通りです。
- ε(取り出し):コンテキストから現在の値を抽出する——「コンテキストから現在の値を取り出す」。プログラムが環境を読みたい? 手を伸ばせばすぐに取り出せます。
- δ(複製):コンテキストを 1 部コピーして、ネストされた内側の計算に渡す——「ネストされたアクセスのためにコンテキストを複製する」。内側の関数が環境を読みたい? 環境はそれについて行きます。
論文ではさらにストリームコモナドにも言及しています。D(X) = ℕ → X と書きます。ℕ は自然数(時点と考えてよい)で、ある時刻を与えると 1 つの値を返します。これが特徴づけるのは時系列データへの依存です。たとえば、時計に従って動くコンポーネントや、データストリームに従うコンポーネントなどです。
🐟 たとえ話をしましょう。モナドの中のプログラムは密閉された箱のようなもので、世界を外に閉め出しています。コモナドの中のプログラムは魚のようなもので、常に水(環境)に浸かっており、呼吸(計算の各ステップ)のたびに水から酸素を得ています。魚がどこに泳いでも、水はついて行きます。これが「コンテキスト依存の計算」です。
つまり「コモナド的コーエフェクト」を一言でいうと、プログラムの結果はそれが置かれた環境に依存し、環境は必要なものを自発的に手渡してくれる、ということです。
3. グレード付きコーエフェクト:「どれだけ必要か」に重さのラベルを貼る
「プログラムは環境に依存する」と言うだけでは不十分です。どれだけ依存するかも重要です。ある変数が 0 回使われるのか 100 回使われるのかで、リソース消費の差は天と地ほどあります。
グレード付きコーエフェクト(graded coeffects)のやり方は、すべての「要求」に重さのラベルを貼ることです。論文のこのラベル体系は 1 つの半環で、次のように書きます。
S = (S, ≤, +, ×, 0, 1)
記号に怯えないでください。「加減乗除ができる計量システム」と理解すれば十分です。ラベルには 4 つの意味があり、とても日常的なものです。
| ラベル | 意味 | たとえ |
|---|---|---|
| 0 | 未使用:この変数は最初から最後まで一度も使われない | 一度も電源を入れたことのない電化製品 |
| 1 | 線形使用:ちょうど 1 回だけ使われる | 使い捨ての食器、使ったら捨てる |
| n | 有界使用:最大 n 回まで使われる | 回数限定の体験券 |
| ∞ | 無制限使用:何回使ってもよい | 無制限のデータ通信プラン |
2 つの演算は 2 つの「組み合わせ方」に対応します。
- ×(乗算):順次組み合わせ——A を終えてから B を行い、重さは掛け合わせます。たとえば「ファイルを 1 回読み、さらにもう 1 回読む」は 1 × 1 です。
- +(加算):並行組み合わせ——A と B を同時に実行し、重さは足し合わせます。
💡 たとえ話をしましょう。家に水道と電気のメーターを設置するようなものです。「このコードはこのファイルを最大 n 回まで読む」と宣言すれば、システムは実行する前に超過していないかを検査できます。2 つのタスクが同時に動き出せば、システムはそれらの使用量を合計して総勘定をします。
この「計量システム」があれば、論文によれば1 つの統一的な代数的枠組みの中で、これまでできなかったいくつかのことができるようになります。
- 正確なリソース追跡——この変数が実際に何回使われたかが、一目で分かる。
- 感度分析——誰の入力が誰の出力に影響するか、どれだけ影響するかが、一目瞭然。
- 情報フロー制御——機密データが安全でない場所へ流れ得るかどうかを、全過程で検査できる。
一言でいえば、グレード付きコーエフェクト = コーエフェクト + 使用量の帳簿であり、「何が必要か」を「どれだけ必要か」へとアップグレードするものです。
4. 静的から動的へ:コンポーザビリティとの関係
では、この論文が本当に関心を持つ問題に戻りましょう。動的コンポーザビリティです(覚えていますか? コンポーネントは装着でき、取り外せ、取り外しても跡を残さない)。論文の 2.3 節は、エフェクトとコーエフェクトをそれぞれコンポーザビリティの 2 つの次元に対応づけています。
- 時間的コンポーザビリティ → 状態を持つエフェクト。時間的コンポーザビリティは「コンポーネントによる共有環境への変更が、コンポーネントのアンインストール時に可逆であること」を要求します。環境を本当に永続的に変えるのは状態を持つエフェクト(ファイルへの書き込み、データベースの変更)です。この変更を可逆にするには、その変換に逆変換が存在しなければなりません(取り消せること)。
- 空間的コンポーザビリティ → コーエフェクト。空間的コンポーザビリティは「コンポーネント間の依存を宣言し、リアクティブな方式で管理すること」を要求します。これらの依存はまさにコーエフェクトが捕捉するものです。依存を管理するとは、環境が実際に提供するものに基づいて、各依存を一つずつ解決していくことです。
| コンポーザビリティの次元 | 問うこと | 対応する概念 |
|---|---|---|
| 時間的コンポーザビリティ | アンインストール後、環境は復元できるか? | 状態を持つエフェクト(変更には逆変換が必要) |
| 空間的コンポーザビリティ | コンポーネントは何に依存するか? 誰が提供するか? | コーエフェクト(依存の宣言 + リアクティブな解決) |
さて、問題です。古典的なエフェクトシステムやコーエフェクトシステムは、動的なコンポジションをそのまま支えられるでしょうか?
論文の答えは率直です。できません。なぜなら、それらは静的な道具だからです。
- エフェクトは字句的に固定されたスコープの中で追跡され、コンパイル時のハンドラーによって消去されます。
- コーエフェクトの注釈は、実行前にすでに確定しているコンテキストに基づいて検証されます。
この 2 つは「世界はコンパイル時に決まっている」という仮定の下では成り立ちますが、動的コンポジションの世界はそうではありません。
| 静的システムの仮定 | 動的コンポジションの現実 |
|---|---|
| スコープは字句的に書き込まれて固定されている | プラグインはデプロイ後に読み込まれるため、固定スコープでは囲い込めない |
| コンテキストはコンパイル時に確定できる | 依存は実行時の設定から生じることがあり、コンパイル時には到底予見できない |
⚠️ そこで論文はここで視点を転換します。静的型システムにさらに注釈を追加して拡張するのではなく、エフェクトとコーエフェクトの概念的構造を具体化(reify)し、ランタイムがそれらを直接操作できるようにする。こうして、静的な場合にこれらのシステムが提供していた保証を、実行時に動的に築き上げるのです。
これが「コンパイル時の証明」から「ランタイムの仕組み」への決定的な飛躍です。どのように具体化し、どのようにランタイムに操作させるのかは、論文の第 3 節の内容です。次の課でお会いしましょう。
ポイントの振り返り
この課は情報量が少なくありませんが、次の 5 文だけ覚えておけば十分です。
- 双対:エフェクトは「私は何を変えたか」を問い(プログラムの世界への影響)、コーエフェクトは「私は何を必要とするか」を問います(世界のプログラムへの制約)。出前の注文はエフェクト、働いて給料をもらうのはコーエフェクトです。
- コーエフェクトが注釈するのはコンテキスト:
Γcoeffect ⊢ t : Tにおいて、コンテキストはコーエフェクト代数の要素で注釈され、計算の環境への要求——アクセスするリソース、備えるべき能力、依存するサービス——を記述します。 - コモナド = 環境に浸かった計算:環境コモナド
D(X) = E × Xは、すべての計算が環境を携えて走ることを表します。ε は環境から現在の値を取り出し、δ は環境を複製してネストされた計算に渡します。 - グレード付きコーエフェクト = 使用量の帳簿:半環
S = (S, ≤, +, ×, 0, 1)のラベルが使用を数量化します——0 は未使用、1 は線形、n は有界、∞ は無制限。× は順次組み合わせ、+ は並行組み合わせで、リソース追跡、感度分析、情報フロー制御を支えます。 - 静的から動的へ:時間的コンポーザビリティは状態を持つエフェクト(可逆な変更)に対応し、空間的コンポーザビリティはコーエフェクト(依存)に対応します。静的システムは実行時に読み込まれるプラグインを囲い込めないため、論文はエフェクト/コーエフェクトをランタイムが直接操作できる仕組みへと引き上げます。
🚀 次の課では、論文第 3 節の最初の主役であるロールバック可能なエフェクトに入ります。「装着でき、取り外せる」。エフェクトはどうすれば実行時に本当に取り消せるのでしょうか? それこそが時間的コンポーザビリティへの答えです。
確認テスト · コーエフェクト
回答を終えたら「解答を送信」をクリックすると、正誤と解説を確認できます。
