エフェクトシステムは、関数が起こす副作用(状態の変更、例外、入出力など)を型の一部として追跡し、未処理の副作用をコンパイル時に検出する仕組みです。Algebraic Effects(代数的エフェクト)は、その副作用を「操作の宣言」と「ハンドラによる解釈」に分ける設計で、例外に継続の再開を加えたものと考えると理解が早くなります。この記事では両者の関係を整理したうえで、OCaml 5・Koka・Effekt・Unison、そしてHaskellの各ライブラリが2026年9月時点でどこまで実用に耐えるかを、バージョンと公式記述に当たって示します。
まとめ:エフェクトシステムとAlgebraic Effectsの要点
- エフェクトシステム(型で効果を追跡する)とエフェクトハンドラ(効果を捕まえて解釈する制御機構)は別の機能で、両方を備えるのはKokaやEffekt、片方だけなのがOCaml 5です。
- OCaml 5は効果ハンドラを持ちますが、公式マニュアルが「effect safety を提供しない」と明記しており、未処理の効果は実行時の
Effect.Unhandled例外になります。 - 継続の再開回数は表現できる制御の目安です。0回なら処理の打ち切り、1回なら状態や非同期、複数回なら探索などを表現できますが、OCamlの継続は高々1回しか再開できません。
- Haskellで今選ぶなら
effectful(2026-08-24公開の2.7.1.0)です。一方、extensible-effectsパッケージ自体はHackageの最終公開が2019-01-03で止まっています。 - Rustの標準機能に効果ハンドラはなく、keyword genericsも安定化していません。ReactやJavaScriptの「Algebraic Effects」は言語機能ではなく、説明のための比喩です。
以降では、効果ハンドラの仕組みと、公式資料で確認した各言語の実装状況を説明します。
副作用を型で追跡するエフェクトシステムの構造
関数型に現れる効果の表現
従来も、副作用は状態を保持するオブジェクト、明示的な引数渡し、例外、モナドなど複数の方法で管理されてきました。Haskellのmtlのように、具体的なモナドスタックを決めずに MonadState などの型クラス制約で必要な効果を型に示す方法もあります。エフェクトシステムが変えるのは、型で表せる効果の範囲と、未処理の効果をコンパイラが検出できるかどうかです。
Kokaの公式ブックは、関数型が引数の型・効果の型・結果の型という3つの部分からなると説明し、sqr : (int) -> total int(数学的な全域関数)、divide : (int,int) -> exn int(例外を投げうる)、turing : (tape) -> div int(停止しないかもしれない)、print : (string) -> console ()(コンソールに書く)、rand : () -> ndet int(非決定的)という例を挙げています。total と書かれた関数は副作用を一切持たないことがコンパイラに保証され、console が付いた関数を total の位置では呼べません。純粋性が注釈ではなく型検査の対象になる、というのがエフェクトシステムの実体です。
エフェクトシステムとエフェクトハンドラの区別
この2つはしばしば同一視されますが、分けて考えないと言語選定を誤ります。型で効果を追跡するのがエフェクトシステム、効果の発生をその場で捕まえて意味を与えるのがエフェクトハンドラです。OCaml 5は後者だけを実装しました。OCamlマニュアルの効果ハンドラの章は「Unlike languages such as Eff and Koka, effect handlers in OCaml do not provide effect safety; the compiler does not statically ensure that all the effects performed by the program are handled」と書いており、ハンドラを書き忘れてもコンパイルは通ります。発生時点で Effect.Unhandled 例外が送出されるだけです。
つまり「OCaml 5でエフェクトシステムが入った」という説明は正確ではありません。入ったのは効果ハンドラであり、型検査による網羅保証は現時点で別の課題として残っています。Result型で戻り値に失敗を載せる鉄道指向プログラミングと同じく、どこまでを型に出してどこからを実行時に任せるかという設計判断が言語ごとに違う、と捉えるのが実態に近い理解です。
Algebraic Effectsの動作原理
効果の宣言と発生
Algebraic Effectsでは、まず「何ができるか」だけを宣言し、その意味は与えません。OCaml 5では拡張可能バリアント型 Effect.t に構成子を足す形で宣言します。公式マニュアルの例は次のとおりです。
open Effect
open Effect.Deep
type _ Effect.t += Xchg : int -> int t
let comp1 () = perform (Xchg 0) + perform (Xchg 1)
let () =
let r =
try comp1 () with
| effect (Xchg n), k -> continue k (n + 1)
in
print_int r (* 3 *)
Xchg は「整数を渡すと整数が返る操作」という宣言だけで、実装を持ちません。comp1 はそれを2回呼び、try ... with | effect ... で囲んだハンドラが n + 1 を返すため、結果は 1 + 2 = 3 になります。ここで effect は例外ではなく効果にマッチすることを示すキーワードで、効果ハンドラの機能は5.0で導入され、この例で使うdeepハンドラの構文サポートは5.3で入りました。OCamlの最新安定版は5.5.1(2026-09-04公開)です。
継続の再開回数が決める機能
例外との決定的な違いは、ハンドラが継続 k を受け取る点にあります。k は perform の地点からハンドラまでの「宙吊りになった計算」で、これを何回再開するかが機能を決めます。
- 0回再開(継続を捨てる)=例外。エラーで処理を打ち切る従来の挙動になります。
- 1回再開=状態、環境、非同期I/O、ジェネレータ。実用のほとんどはここに入ります。
- 複数回再開=非決定計算、探索、バックトラック。1つの呼び出しから複数の世界線を走らせます。
Kokaの公式例では、効果宣言と再開を次のように書きます。定義したprint-elemsを呼ぶと、yielded 1、yielded 2、yielded 3を順に出力して走査を終了します。ハンドラ側の resume が継続の再開にあたります。
effect yield
ctl yield( i : int ) : bool
fun traverse( xs : list<int> ) : yield ()
match xs
Cons(x,xx) -> if yield(x) then traverse(xx) else ()
Nil -> ()
fun print-elems() : console ()
with ctl yield(i)
println("yielded " ++ i.show)
resume(i<=2)
traverse([1,2,3,4])
traverse はリストを走査して yield を呼ぶだけで、出力方法も打ち切り条件も知りません。ハンドラ側が resume(i<=2) を返すことで、3以上の要素に達した時点で走査が止まります。呼び出し側と実装側の責務がここで分離されます。この「自分を呼ぶ関数がスタックを積む」感覚は再帰関数のスタックと末尾再帰の扱いと地続きで、継続の捕捉はそのスタック片を値として持ち運ぶ操作にあたります。
モナド変換子・Extensible Effectsとの違い
モナド変換子のリフトと効果順序
Haskellで複数の効果を扱う古典的な方法がモナド変換子です。StateT s (ExceptT e IO) のように積み上げますが、実務では2つの負担が出ます。ひとつは型クラスによる自動的な持ち上げを使えない操作で lift を重ねる記述量、もうひとつは積む順序が意味論を変えることです。StateT と ExceptT の順序を入れ替えると、例外発生時に状態変更を残すか捨てるかが変わります。
この差は実装側でも認識されており、effectful の公式ドキュメントは「unlike the StateT monad transformer from the transformers library, the State effect doesn’t discard state updates when an exception is received」と明記しています。例外が発生したときに状態更新が残るかどうかという挙動を、ライブラリ側が先に決めてくれるわけです。
Extensible Effectsの型レベル効果リスト
Extensible Effectsは、モナドを積むのではなく「この計算が使う効果の集合」を型レベルのリストで持ち、Open Unionで振り分ける方式です。State Int :> es のように必要な効果だけを制約として書けるので、利用側で具体的なモナドスタックに依存せず記述できます。ただし、ハンドラの適用順序によって結果が変わる場合があります。
名称と実装は分けて考えてください。手法としてのExtensible Effectsは effectful や polysemy に受け継がれていますが、extensible-effects というパッケージそのものはHackageでの最終公開が5.0.0.1の2019-01-03で、以後7年以上にわたって新しいバージョンが出ていません。新規プロジェクトでこの名前のパッケージを選ぶ理由は、2026年9月時点でありません。
モナド変換子・効果ライブラリ・言語組み込み機能の選定基準
| 方式 | 効果の指定 | 順序依存 | 主な選択理由 |
|---|---|---|---|
| モナド変換子 | スタックの型 | あり | 既存コード・依存の少なさ |
| Extensible Effects系ライブラリ | 型レベルの効果リスト | ハンドラ順序に依存する場合あり | Haskellで効果を分離したい |
| 言語組み込みのAlgebraic Effects | 効果型の有無は言語による | ハンドラの配置に依存 | 未処理効果の静的検出・直接スタイル |
Haskellで方式を選ぶときの判断は、既存コードの状態で大きく分かれます。mtlが深く入っているコードベースでは、移行コストが効果分離の利点を上回る場面が多くあります。一方、新規のアプリケーションで効果ごとにテスト用の差し替えを作りたい場合は、ライブラリを導入する具体的な理由になります。言語組み込みの機能を選べるかどうかは、そもそも使用言語の側で決まります。
言語とライブラリの実装状況(2026年9月時点)
OCaml・Koka・Effekt・Unisonの効果追跡と実用性
| 言語 | 最新版 | 公開日 | 効果を型で追跡 | 言語内の呼称 |
|---|---|---|---|---|
| OCaml | 5.5.1 | 2026-09-04 | しない | effect handlers |
| Koka | v3.2.9 | 2026-09-18 | する | effect handlers |
| Effekt | v0.81.0 | 2026-09-21 | する | effects / capabilities |
| Unison | release/1.4.0 | 2026-08-19 | する | abilities |
Unisonの公式ドキュメントは「Abilities are Unison’s implementation of algebraic effects, as they’re known in the literature」と述べており、呼称が違うだけで同じ概念です。一方でKokaの公式ブックは冒頭に「Koka v3 is a research language that is currently under development and not ready for production use.」と置き、Effektの公式READMEも「Effekt is a research-level language. We are actively working on it and the language (and everything else) is very likely to change.」と明記しています。この2つは提供元自身が研究段階と位置付けているため、業務システムの土台には選べません。
残るOCamlとUnisonは性格が違います。OCamlは長い運用実績を持つ汎用言語で、効果ハンドラだけが新しい機能です。Unisonは提供元自身が自社のミッションクリティカルな用途での運用を公開している一方、コードベース管理の仕組みごと新しい言語です。必要なライブラリ、運用環境、チームの経験を突き合わせて候補を絞ることになります。
Haskellのライブラリ選択
| ライブラリ | 最新版 | Hackage最終公開 | 状況 |
|---|---|---|---|
| effectful | 2.7.1.0 | 2026-08-24 | 更新継続 |
| polysemy | 1.9.2.0 | 2024-06-03 | 更新が鈍化 |
| freer-simple | 1.2.1.2 | 2022-01-07 | 新規リリースなし |
| extensible-effects | 5.0.0.1 | 2019-01-03 | 新規リリースなし |
状態管理や依存の差し替えを主目的とし、保守状況を重視するなら effectful が候補です。ただし、継続を捕捉して後から再開するハンドラはサポートしていません。Effectful.State.Static.Local と Effectful.State.Static.Shared のようにスレッドローカル版と共有版が分かれており、runState / evalState / execState という既存のmtl利用者が読める名前を使えます。
性能の話をするときに注意したいのが、GHCの限定継続プリミティブとの関係です。GHC Proposal #313「Delimited continuation primops」は2021-01-18にマージされ、GHC 9.6.1で prompt# と control0# が入りました。リリースノートは「no safe API to access this functionality is provided anywhere in base」と断り、ライブラリ作者が直接使うことを前提にしています。これを土台にした eff が本命視された時期がありましたが、effectful のREADMEは「the development of eff has stalled due to a few subtle issues related to its use of delimited continuations underneath」と記しています。effectful 自身は限定継続ではなくIOベースの実装を選ぶことで、実用速度と既存エコシステムとの接続を優先しました。GHC本体の動きはHaskellが実際に使われている領域を見るとどこに効くか判断しやすくなります。
Rust・Python・JavaScriptに「ある」と誤解されやすいもの
Rustのkeyword genericsと効果ハンドラの区別
「rust effect system」「rust algebraic effects」で検索して辿り着く情報の多くは、keyword generics(効果多相)の提案です。これは効果ハンドラではなく、async や const といった修飾子に対してジェネリックなコードを書けるようにする構想でした。現状を日付で押さえます。トラッキングIssue「Tracking Issue for keyword generics」(#102090) は2024-02-28にクローズされましたが、同Issueには2024-07-25付で「we’re still working on effect generics」という開発者の説明があり、実装の前提となる ~const の整理が先だと述べられています。実際、PR #126552「Remove use of const traits (and feature(effects)) from stdlib」が2024-06-22にマージされ、標準ライブラリから feature(effects) の利用が外されました。ただしkeyword-generics-initiativeリポジトリの最終pushは2024-08-12で、以後の公開された進展は確認できません。
したがって、Rust標準の効果ハンドラが使えることを前提とした設計は避けてください。標準機能として提供されているのは Result 型による失敗の明示と async による非同期です。型付きの効果を扱う実験的クレートは存在し、effing-mad は「This library brings typed effects to Rust in a flexible and composable way」と説明していますが、公開されている版は2023-03-27の0.1.0だけなので、業務で採用するならツールチェーンの対応と保守状況を自分で確かめる必要があります。ネイティブなスタック捕捉を前提としたコードはそのまま移植できないものの、ジェネレータや明示的な計算表現に置き換えて効果とハンドラを組み直す道は残ります。
Reactの着想元とPythonのジェネレータによる近似
ReactのSuspenseやHooksの解説で「Algebraic Effects」という語を見た人は多いはずです。出どころはDan Abramov氏の記事「Algebraic Effects for the Rest of Us」ですが、そこで使われている perform や try ... handle は、記事中で明示的に架空のJavaScript方言として導入されたものです。Suspenseの仕組みについても「This isn’t an algebraic effect per se, even though this trick was inspired by them.」と本人が書いています。React内部でPromiseをthrowして再試行する手法は着想元が同じというだけで、言語機能としての代数的エフェクトではありません。
Pythonにも言語機能としての効果ハンドラはありません。ジェネレータやコルーチンで「呼び出し側に制御を戻して値を受け取る」形を作れば片方向の再開は近似できますが、中間の関数すべてをジェネレータにする必要があり、Algebraic Effectsの利点である透過性が失われます。JavaScript側の見通しとしては、WebAssemblyのStack Switching提案がproposalsリポジトリでPhase 3(Implementation Phase)にあり、仮にここが進んでも言語構文ではなくランタイムの下地が整う段階にとどまります。HaskellコードをJavaScriptへ変換するGHCJSのように、効果を持つ言語をWeb上で動かす経路のほうが現実的です。
導入判断:採用を見送るべき場面と運用コスト
多重再開とリソース解放の非互換
継続を複数回再開する使い方は、既存のリソース管理と真正面からぶつかります。ファイルハンドルやロックを try ... finally 相当で解放するコードの内側で効果を発生させ、継続を複数回再開すると、解放処理が重複する可能性があります。ただし終了処理の挙動は実装に依存し、Kokaのfinallyは効果操作が再開しない場合にも実行されます。探索やバックトラックを目的に多重再開を使うなら、その区間ではリソースを掴まない設計が必須です。この制約を守れないコードベースに後付けで導入するのは避けてください。
デバッグと型検査の負担
効果ハンドラは制御を非局所的に飛ばすため、スタックトレースが「どこで発生してどのハンドラが処理したか」を素直に示しません。OCaml 5のように効果が型に出ない言語では、ハンドラの書き忘れがテストで通っていない経路にそのまま残ります。Haskellのライブラリ方式では逆に、型レベルの効果リストが膨らむと型エラーが長大になり、原因箇所の特定に時間がかかります。
採用の価値が明確なのは、効果ごとに実装を差し替えてテストしたい、あるいは非同期とジェネレータを同じ枠組みで書きたい、という要求が具体的にある場合です。「副作用を型で管理したい」という抽象的な動機だけなら、Erlang VM上で動くGleamのような型安全な関数型言語を選ぶ、あるいは既存言語の中で副作用を持つ層を境界で切る設計のほうが、学習コストと保守性の釣り合いが取れます。
よくある質問
Algebraic Effectsとモナドは何が違いますか?
モナドは計算を合成するための抽象化です。Haskellでは IO a などで効果のある計算を表現し、複数の効果を組み合わせる方法の一つとしてモナド変換子を使います。Algebraic Effectsは効果を「操作の呼び出し」として表現し、意味付けをハンドラに外出しします。利用側では個々の効果を操作として記述できますが、ハンドラを適用する順序によって、例外発生時に状態を残すかどうかなどの挙動は変わります。書き味の面では、モナドが計算を包むのに対し、Algebraic Effectsは通常の関数呼び出しと同じ見た目のまま効果を扱えます。
OCaml 5でエフェクトシステムは使えますか?
使えるのは効果ハンドラで、効果を型で追跡するエフェクトシステムは含まれていません。公式マニュアルがeffect safetyを提供しないと明記しており、ハンドラの書き忘れはコンパイル時ではなく実行時の Effect.Unhandled 例外として現れます。最新版は5.5.1(2026-09-04)です。
Haskellではどのエフェクトライブラリを選ぶべきですか?
新規リリースが続いている effectful(2.7.1.0・2026-08-24公開)が現実的な選択です。ただし継続を捕捉して後から再開するハンドラは扱えないため、コルーチンや非決定計算が要るなら別の手段を用意してください。既存コードがmtl中心なら、移行せず変換子のまま進める判断も十分に成立します。
Rustに代数的エフェクトは入りますか?
2026年9月時点で時期の見通しは立っていません。keyword generics(効果多相)のトラッキングIssue #102090は2024-02-28にクローズされ、開発者は同Issueで作業継続を表明しているものの、専用リポジトリの更新は2024-08-12で止まっています。標準ライブラリからも feature(effects) の利用は2024-06-22に外されました。なおkeyword genericsは効果ハンドラとは別の機能で、これが入ってもOCamlやKokaのようなハンドラが使えるようになるわけではありません。
ReactのSuspenseは代数的エフェクトですか?
違います。解説記事の著者自身が「This isn’t an algebraic effect per se」と書いており、Promiseをthrowして再試行する実装は着想を得ただけの別物です。JavaScriptに perform や効果ハンドラの構文は存在しません。