エフェクトシステム(effect system)は、関数が起こす副作用(状態の変更、例外、入出力など)を型の一部として追跡し、処理されない副作用をコンパイル時に見つける仕組みです。Algebraic Effects(代数的エフェクト)は、その副作用を「操作の宣言」と「ハンドラによる解釈」に分ける設計で、例外に「処理を再開する」操作を足したものと考えると理解が早くなります。この記事では両者の関係を整理したうえで、Javaの検査例外のような身近な例から、OCaml 5・Koka・Effekt・Unison・Flix、Haskellの各ライブラリまでを、2026年11月時点のバージョンと公式記述で比べます。
まとめ:エフェクトシステムとAlgebraic Effectsの要点
- エフェクトシステム(型で効果を追跡する仕組み)とエフェクトハンドラ(発生した効果を捕まえて意味を与える制御機構)は別の機能です。両方を備えるのはKoka・Effekt・Unison・Flix、ハンドラだけを持つのがOCaml 5です。
- Javaの検査例外は「例外」という1種類の効果だけを追跡する限定的なエフェクトシステムで、Scala 3は同じ考え方を
CanThrowとして実験的に提供しています。 - 継続を何回再開できるかで表現できる制御が決まります。OCamlの継続は1回限りで、2回目は
Continuation_already_resumed例外になります。Flixは複数回の再開に対応しています。 - Haskellで今選ぶなら、更新が続きGHC 9.14.1での動作確認もある
effectful(2.7.1.0)です。継続の捕捉が要るならheftiaが候補ですが、0.7.0.0はbase 4.22未満の制約があり、GHC 9.14(base 4.22.0.0)にはそのままでは入りません。 - Rustの標準機能に効果ハンドラはなく、TypeScriptのEffectライブラリやReactの「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 の位置では呼べません。純粋性が注釈ではなく型検査の対象になる、というのがエフェクトシステムの実体です。純粋関数そのものの考え方は関数型プログラミングの純粋関数と不変性の解説で扱っています。
Javaの検査例外とScala 3のCanThrowという限定的な実例
エフェクトシステムは研究言語だけのものではありません。Javaの検査例外は、効果を1種類に絞ったエフェクトシステムと見なせます。Java言語仕様(SE 25)の§11.2は「The Java programming language requires that a program contains handlers for checked exceptions which can result from execution of a method or constructor」と定めており、throws IOException はメソッドの型に付いた効果の宣言、try ... catch はそのハンドラにあたります。ただし追跡できるのは例外だけで、catchした地点から元の処理へ戻る手段はありません。
Scala 3はこの考え方を型の仕組みで作り直す実験を続けています。Scala 3リファレンスのCanThrow Capabilitiesは import language.experimental.saferExceptions で有効になり、def m(x: T): U throws E を (using CanThrow[E]) という能力(capability)の引数に読み替えます。同じページは「algebraic effects have been characterized as being equivalent to exceptions with an additional resume operation」と書き、例外検査の技法が他の代数的エフェクトへ一般化できる可能性に触れています。Scala 3.10.0(2026-10-12公開)のリファレンスでは、能力の参照を追跡するcapture checkingも「Both extensions are experimental, which means that details can still change.」という実験的機能の扱いです。Javaの検査例外に「再開」を足したものがAlgebraic Effects、と捉えると次章の仕組みが読みやすくなります。
エフェクトシステムとエフェクトハンドラの区別
この2つはしばしば同一視されますが、分けて考えないと言語選定を誤ります。型で効果を追跡するのがエフェクトシステム、効果の発生をその場で捕まえて意味を与えるのがエフェクトハンドラです。OCaml 5は後者だけを実装しました。OCaml 5.5マニュアルの効果ハンドラの章は「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の動作原理
「代数的」が指す理論上の意味
名前の「代数的」は、効果を等式で縛られた操作の集まり(代数理論)として扱う数学の枠組みから来ています。Gordon PlotkinとMatija Pretnarの論文「Handling Algebraic Effects」(Logical Methods in Computer Science、2013年)は要旨で、代数的エフェクトを「computational effects that can be represented by an equational theory whose operations produce the effects at hand」と定義し、例外・状態・非決定性・対話的な入出力・時間を例に挙げています。そのうえで例外ハンドラを任意の代数的エフェクトへ一般化したのが、いまのエフェクトハンドラの原型です。数学側から順に追いたい場合は、Andrej Bauerの解説「What is algebraic about algebraic effects and handlers?」(2018年初版・2019年改訂)が普遍代数の基本から説明しています。実務で使うだけなら、この理論を知らなくても次の2節の仕組みで足ります。
効果の宣言と発生
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.2(2026-10-23公開)です。
継続の再開回数が決める機能
例外との決定的な違いは、ハンドラが継続 k を受け取る点にあります。k は perform の地点からハンドラまでの「宙吊りになった計算」で、これを何回再開するかで表現できる機能が決まります。
- 0回再開(継続を捨てる)=例外。エラーで処理を打ち切る従来の挙動です。
- 1回再開=状態、環境、非同期I/O、ジェネレータ。実用のほとんどはここに入ります。
- 複数回再開=非決定計算、探索、バックトラック。1つの呼び出しから複数の分岐を走らせます。
OCamlは1回再開までです。マニュアルは継続を「exactly once」使うよう求め、2回再開しようとすると Continuation_already_resumed 例外が出ると明記しています。並行処理を主目的に、スタックをコピーしない速さを優先した設計です。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を出力した後にfalseで再開され、走査が止まります。呼び出し側と実装側の責務がここで分かれます。継続の捕捉は、再帰関数のスタックと末尾再帰で扱う呼び出しスタックの一部を、値として持ち運ぶ操作にあたります。
モナド変換子・Extensible Effectsとの違い
モナド変換子のリフトと効果順序
Haskellで複数の効果を扱う古典的な方法がモナド変換子です。StateT s (ExceptT e IO) のように積み上げますが、実務では2つの負担が出ます。型クラスによる自動の持ち上げが効かない操作で lift を重ねる記述量と、積む順序で意味が変わることです。StateT と ExceptT の順序を入れ替えると、例外発生時に状態の変更を残すか捨てるかが変わります。
effectful の公式ドキュメント(Effectful.State.Static.Local)は「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で、2026年11月時点では約7年10か月、新しい版が出ていません。保守状況を重視する新規の業務アプリケーションでは、このパッケージを第一候補には勧めません。
高階エフェクトの対応範囲と継続捕捉
ライブラリを比べるときに見落とされやすいのが高階エフェクトです。ログ出力の log :: String -> m () のように値だけを引数に取る効果を一階、処理時間を測る profile :: String -> m a -> m a や分散トレーシングのスパン、bracket のように「効果を持つ計算そのもの」を引数に取る効果を高階と呼びます。実務で欲しくなるのは後者で、ここがライブラリごとの差になります。
effectful は高階エフェクトを扱えます。Effectful.Dispatch.Dynamic のドキュメントには「First order and higher order effects」という節があり、Profile :: String -> m a -> Profiling m a の解釈例が載っています。ただしREADMEが書くとおり、継続を捕まえて後から(複数回)再開するハンドラには対応していません。高階エフェクトと継続の捕捉を両立させると主張しているのが heftia で、Hackageの説明は「generalizes “Algebraic Effects and Handlers” to higher-order effects」とし、POPL 2023の論文「Hefty Algebras」を土台に挙げています。
heftiaを選ぶ前に確認したいのは保守状況です。最新の0.7.0.0は2025-04-24公開で、依存に base (>=4.17 && <4.22) を指定しています。GHC 9.14.1に同梱されるのは base 4.22.0.0なので、9.14では制約を外さない限り入りません。READMEの「Status (Update: 2025-08-02)」は、性能面の弱点を解消する新方式を別リポジトリで試していると書いています。GHC 9.14のLTS運用に合わせて開発するなら、現時点ではeffectfulを軸にし、heftiaは検証用にGHC 9.12系で試すのが安全です。
モナド変換子・効果ライブラリ・言語組み込み機能の選定基準
| 方式 | 効果の指定 | 順序依存 | 主な選択理由 |
|---|---|---|---|
| モナド変換子 | スタックの型 | あり | 既存コード・依存の少なさ |
| Extensible Effects系ライブラリ | 型レベルの効果リスト | ハンドラ順序に依存する場合あり | Haskellで効果を分離したい |
| 言語組み込みのAlgebraic Effects | 効果型の有無は言語による | ハンドラの配置に依存 | 未処理効果の静的検出・直接スタイル |
Haskellで方式を選ぶときは、既存コードの状態で判断が分かれます。mtlが深く入っているコードベースでは、移行コストが効果分離の利点を上回る場面が多くあります。新規のアプリケーションで効果ごとにテスト用の差し替えを作りたい場合は、ライブラリを入れる具体的な理由になります。
言語とライブラリの実装状況(2026年11月時点)
OCaml・Koka・Effekt・Unison・Flixの効果追跡と実用性
| 言語 | 最新版 | 公開日 | 効果を型で追跡 | 言語内の呼称 |
|---|---|---|---|---|
| OCaml | 5.5.2 | 2026-10-23 | しない | effect handlers |
| Koka | v3.2.9 | 2026-09-18 | する | effect handlers |
| Effekt | v0.83.0 | 2026-11-02 | する | effects / capabilities |
| Unison | release/1.5.0 | 2026-10-02 | する | abilities |
| Flix | v0.79.0 | 2026-10-18 | する | effects and handlers |
版と公開日は、各バージョンからリンクしたGitHub Releaseの値です。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つは業務システムの土台には選べません。
Flixは表の中で毛色が違います。Flixの公式ドキュメントは「Flix supports algebraic effects and handlers in the style of Eff and Koka. Flix effect handlers use dynamic scope, deep handlers, and support multiple resumptions.」と説明しています。言語の紹介ページはJVMバイトコードへのコンパイルとJavaのクラス・メソッドとの相互運用を特徴に挙げており、既存のJava資産と組み合わせられる点が他の4言語との違いです。型と効果は推論されますが、Kokaも推論に対応しているので、この点は両者の差になりません。ただし版は1.0未満で、2026年9月28日から10月18日までに3つのリリース(v0.77.0〜v0.79.0)が出る速さで変わっているため、採用するなら言語の変更に追随できる小さな範囲から始めることになります。
OCamlとUnisonは性格が違います。OCamlは長い運用実績を持つ汎用言語で、効果ハンドラだけが新しい機能です。Unisonは提供元自身が自社のミッションクリティカルな用途での運用を公開している一方、コードベース管理の仕組みごと新しい言語です。必要なライブラリ、運用環境、チームの経験を突き合わせて候補を絞ってください。
Haskellのライブラリ選択
| ライブラリ | 最新版 | Hackage最終公開 | 特徴 |
|---|---|---|---|
| effectful | 2.7.1.0 | 2026-08-24 | IOベース・高階対応・継続捕捉なし |
| bluefin | 0.10.1.0 | 2026-09-25 | 効果を値の引数で渡す |
| heftia | 0.7.0.0 | 2025-04-24 | 高階+継続捕捉・base 4.22未満 |
| polysemy | 1.9.2.0 | 2024-06-03 | 高階対応・2年以上新しい版なし |
| freer-simple | 1.2.1.2 | 2022-01-07 | 新規リリースなし |
| extensible-effects | 5.0.0.1 | 2019-01-03 | 新規リリースなし |
状態管理や依存の差し替えを主目的とし、保守状況を重視するなら effectful が第一候補です。Hackageの「Tested with」にはGHC 9.6.7から9.14.1までが並び、runState / evalState / execState というmtl利用者が読める名前を使えます。bluefin は「effects are accessed explicitly through value-level capabilities which occur as arguments to effectful operations」と説明される方式で、同じ型の状態を2つ同時に持つような場面で区別が簡単になります。
性能を論じるときは、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」と記しています。Haskellがどんな現場で使われているかはHaskellの得意分野と仕事の実態にまとめています。
Rust・Python・TypeScriptに「ある」と誤解されやすいもの
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」という開発者の説明があります。一方、標準ライブラリからはPR #126552で feature(effects) の利用が2024-06-22に外されました。イニシアチブのリポジトリは現在 rust-lang/effects-initiative という名前で、READMEの進行表はDevelopment(開発中)、Feature complete と Stabilized は未着手のままです。最終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.」と書いています。PromiseをthrowしてReactが再試行する手法は着想元が同じというだけで、言語機能としての代数的エフェクトではありません。
Pythonにも言語機能としての効果ハンドラはありません。ジェネレータやコルーチンで「呼び出し側に制御を戻して値を受け取る」形を作れば1回の再開は近似できますが、途中の関数すべてをジェネレータにする必要があり、効果が呼び出し階層を素通りするというAlgebraic Effectsの利点は失われます。実行環境側では、WebAssemblyのStack Switching提案がproposalsリポジトリでPhase 3(Implementation Phase)にあります。公式の実装状況表ではChromeでも実験用フラグが必要で、Wasmtimeも wasm_stack_switching の既定値は無効です。これは効果ハンドラを持つ言語をWasmへコンパイルするための下地で、JavaScriptやPythonに構文が増える話ではありません。Wasmの仕組みはWebAssemblyの仕組みとJavaScriptからの操作を参照してください。
TypeScriptのEffectライブラリが型で追跡するもの
TypeScriptで「effect system」を探すと、npmパッケージ effect(2026-11-07時点の最新は4.1.2・2026-11-02公開)に行き当たります。公式ドキュメントはその中心の型を Effect<Success, Error, Requirements> と示し、成功値・失敗・実行に必要な依存の3つを型パラメータで追跡します。失敗と依存が型に出る点で、エフェクトシステムの考え方に近い設計です。
違いは、利用者が定義した操作を継続ごと捕まえて再開するハンドラを中心に置いていない点です。依存は Effect.provideService などでサービスの実装を渡して解消します。テスト時に実装を差し替える用途はこれで満たせますが、探索やバックトラックのように継続を複数回再開する制御は、この型の仕組みからは出てきません。GHCJSのようにHaskellをJavaScriptへ変換する経路も含め、Webで本物の効果ハンドラを使いたい場合は、ハンドラを持つ言語からのコンパイルを考えることになります。
導入判断:採用を見送るべき場面と運用コスト
多重再開時のリソース取得・解放の管理
継続を複数回再開する使い方は、既存のリソース管理とぶつかります。ファイルハンドルやロックを try ... finally 相当で解放するコードの内側で効果を発生させ、継続を複数回再開すると、解放処理が重複するおそれがあります。終了処理の挙動は実装に依存し、Kokaの finally は効果操作が再開しない場合にも実行されます。探索やバックトラックで多重再開を使う場合は、再開ごとのリソース取得・解放を管理する必要があります。その管理を保証できない区間では、リソースを保持しない設計にしてください。この制約を守れないコードベースに後から入れるのは避けてください。
デバッグと型検査の負担
効果ハンドラは制御を離れた場所へ飛ばすため、スタックトレースが「どこで発生してどのハンドラが処理したか」を素直に示しません。OCaml 5のように効果が型に出ない言語では、ハンドラの書き忘れがテストで通っていない経路にそのまま残ります。Haskellのライブラリ方式では逆に、型レベルの効果リストが膨らむと型エラーが長くなり、原因の特定に時間がかかります。
採用の価値がはっきりしているのは、効果ごとに実装を差し替えてテストしたい、非同期とジェネレータを同じ枠組みで書きたい、といった要求が具体的にある場合です。「副作用を型で管理したい」という抽象的な動機だけなら、Erlang VMとJavaScriptで動く型安全なGleamへの移行を急ぐより、既存言語で副作用を持つ処理を境界に集める設計を先に検討してください。Gleam自体も純粋言語ではなく、型安全性と副作用の管理は別の判断軸です。
よくある質問
Algebraic Effectsとモナドは何が違いますか?
モナドは計算を合成するための抽象化で、Haskellでは IO a などで効果のある計算を表し、複数の効果はモナド変換子で組み合わせます。Algebraic Effectsは効果を「操作の呼び出し」として書き、意味付けをハンドラに外出しします。書き味の面では、モナドが計算を包むのに対し、Algebraic Effectsは通常の関数呼び出しと同じ見た目のまま効果を扱えます。ただしハンドラを適用する順序で、例外発生時に状態を残すかどうかなどの挙動は変わります。
Javaの検査例外はエフェクトシステムですか?
例外という1種類の効果だけを追跡する、限定的なエフェクトシステムと見なせます。throws 節が効果の宣言、catch がハンドラにあたり、Java言語仕様§11.2では、送出されうる検査例外をcatchで処理するか、throws節で宣言して呼び出し元へ伝播させることが求められます。ただしcatchした地点から元の処理へ値を返して再開する手段は無く、状態や非同期のような他の効果も追跡できません。
OCaml 5でエフェクトシステムは使えますか?
使えるのは効果ハンドラで、効果を型で追跡するエフェクトシステムは含まれていません。公式マニュアルがeffect safetyを提供しないと明記しており、ハンドラの書き忘れはコンパイル時ではなく実行時の Effect.Unhandled 例外として現れます。継続の再開は1回限りです。最新安定版は5.5.2(2026-10-23)です。
Haskellではどのエフェクトライブラリを選ぶべきですか?
新しい版が出続けていて、GHC 9.14.1での動作確認もある effectful(2.7.1.0・2026-08-24公開)が現実的な選択です。継続を捕まえて後から再開するハンドラは扱えないため、コルーチンや非決定計算が要るならheftiaを検討しますが、0.7.0.0はGHC 9.14のbaseに対応していません。既存コードがmtl中心なら、移行せず変換子のまま進める判断も十分に成り立ちます。
Rustに代数的エフェクトは入りますか?
2026年11月時点で時期の見通しは立っていません。keyword generics(効果多相)のトラッキングIssue #102090は2024-02-28にクローズされ、開発者は同じIssueで作業継続を表明しているものの、イニシアチブのリポジトリ(rust-lang/effects-initiative)の最終pushは2024-08-12です。なおkeyword genericsは効果ハンドラとは別の機能で、これが入ってもOCamlやKokaのようなハンドラが使えるようになるわけではありません。