非同期処理、難しいですよね。
現代のプログラミングにおいて、非同期処理や並行処理は必須とも言え、各言語で様々な実装があります。コールバック、promise/future そして async/await など。
複数の概念・実装があるものの、例に漏れず銀の弾丸はなく、難しい非同期処理との戦いは今もなお続いていると言えます。
今回紹介するのはその戦いの武器になるかもしれない概念、代数的エフェクト (Algebraic Effect and Handlers) 1 です。
現時点では代数的エフェクトを使える環境・言語は限られていますが、今知っておけば十年後にすました顔でいられるかもしれません。
はじめに
この記事は基盤開発チームのブログリレー2日目にあたる記事です。
M3 Tech Blog でははじめまして。基盤開発チームの田尻です。 2022 年末に OCaml 5.0 がリリースされて 2 から、今に至るまで、OCaml が流行る時を今か今かと待ち続けています。
OCaml 5.0 では並行並列のプリミティブが導入されました。 そして、並行処理のための機能が冒頭でも述べた「代数的エフェクト」です。
代数的エフェクトの説明に入る前に、まずは、現在の問題の1つを見るところから始めましょう。
あなたの関数には色がある
皆さんは「Function Coloring 問題」をご存知でしょうか。
これは「What color is your function?」3 というブログポストにて提唱された問題です。ブログポスト内では、次のような「色付き関数」のルールが示されています4:
- 全ての関数は(赤か青の)色を持ちます
- 色ごとに関数の呼び出し方法が異なります
- 赤色の関数は赤色の関数からしか呼び出せません
- 赤色の関数を呼び出すことはより面倒です
- いくつかのコアライブラリの関数は赤色です
このルールはあくまで例えですが、特定の機能を想定したものです。何だと思いますか?
正解は、「赤色の関数」は「非同期関数」です。
なぜ関数に色がつくのか
どうしてこんなことになってしまうのでしょうか。
その答えは、非同期関数は結果を待ち合わせる必要があるからです。 これは、単に実行した結果を手に入れるだけでなく、エラーが発生して中断されてしまうことも考慮しなくてはいけません。 処理を中断したり、再開したりするためにはどこまで処理が実行され、どこから再開するべきかをきちんと把握しておく必要があります。5
現在、非同期処理を扱うための手法として、次のようなものがあります:
- コールバック
- promise, future
- async, await
- ジェネレータ
これらの手法では上述のルールのような関数の色付けが行われます。 async, await はルール 4 を解決しましたが、他のルールは残っていると元の記事でも記載されています。 関数を非同期か同期かで分類しているという点はどれも変わりません。
また、元の記事では特に JavaScript について言及していましたが、他の言語に目を向けると色が付くのは非同期関数に限りません。 例えば、Haskell のような純粋関数型言語にはモナドがあり、今回説明したいことに関係しています。 6
代数的エフェクト
この関数に色が付く問題を解決してくれるのが「代数的エフェクト」です。 この概念はよく「再開可能な例外」として説明されます。 元々はモナドに関連して導入された概念 7, 8 になります。
非同期処理に注目して考えてみましょう。 非同期処理の難しさの一端は処理の中断や再開にありました。 代数的エフェクトは例外のように、処理の途中で別の処理を挟み込み、中断したところに戻ってくることができます。
より詳しく疑似コードを用いて見てみましょう。
コード例
JavaScriptに寄せた疑似コードで説明します:
function fetchUserProfile(userId) { // 同期・非同期は関係なく、結果が返ってくるまで待つ const user = perform({ type: 'FetchUser', id: userId }); return { user }; } function main() { try { const profile = fetchUserProfile(123); console.log(profile); } handle (effect) { if (effect.type === 'FetchUser') { // 例えば、非同期的にAPIを呼び出す // 完了したらresumeを呼ぶ setTimeout(() => { resume({ id: effect.id, name: 'Alice' }); }, 100); } } }
これは『Algebraic Effects for the rest of us』9 というブログポストの疑似コードを参考にしたコードで、実際には動作しません。
このサンプルでは2つの関数が記述されています。
この疑似コードには perform と resume という2つのキーワードが登場します。
1つ目の関数 fetchUserProfile では 'FetchUser' を指定したエフェクトが発生する (perform) ことが記述されています。
エフェクトの発生で得た結果は変数に格納され、利用できます。
関数そのものも着色されず、一見して通常の関数です。
2つ目の関数 main では fetchUserProfile を呼んでいます。
もちろんこの関数 main も着色されません。通常の関数のように定義されています。
try のブロックで fetchUserProfile が呼び出され、対応する handle では発生するエフェクトについて分岐し、実際の処理が記述されています。
取得した結果は resume キーワードに渡され、エフェクトが発生した地点 (fetchUserProfile の perform) に戻っていきます。
蓋を開けてみればこれだけのことですが、「何をしたいのか」にフォーカスし、「どう実行するのか」は後でハンドラを差し込むことができるのです。
代数的エフェクトのメリット
代数的エフェクトには、大きく次のようなメリットがあります:
- function coloring が起きない
- ユーザーレベルで多くのものが実装できる
- async, await 風の構文
- ジェネレータ
- 例外処理
- その他モナドで実装できるもの
- DI(依存性注入)もできる
- ハンドラを差し替えることが出来るため、テストの時はモックデータを返すといったことが容易です。
- 純粋関数型とも相性がいい
- 副作用をエフェクトとして区切ることが出来ます。
つまり、非同期処理に限らず様々な応用があります。
代数的エフェクトの課題
銀の弾丸はありませんから、代数的エフェクトにも問題点はあります。 特に、その性質から例外と同様の問題が考えられます。
- 意図しないエフェクトの伝播
- エフェクトは例外のようにハンドルされない場合が考えられます。
- ハンドルされないエフェクトは例外同様にランタイムエラーになる可能性があります。
- エフェクトに対する型システムを導入した言語も存在はしていますが、どれも実験的な言語です。
- 実行効率が悪い
- 例外同様に実装次第ではその動作には効率に関連した懸念があります。
そして、何よりも最初に説明したように「実装されている言語のほとんどが研究のための実験的なものばかり」という点が挙げられます。
私の知る限り Production ready な言語はただ1つしかありません。 OCaml です。
OCaml 5.0 では並行処理を取り扱うためにこの代数的エフェクトが導入され、現在も関連機能のアップデートが続いています。
おわりに
という訳で、代数的エフェクトという概念について、非同期処理の観点から説明してきました。
ところで、実際のところ、この概念はこれまでの非同期処理を置き換えるようなものではありませんし、そもそも非同期処理のためのものではありません。 より広範な範囲に応用できる面白い概念であり、これから多くの言語で取り入れられる可能性を秘めています。
もし興味の出てきた方はぜひ OCaml やその他の実験的な言語 10 を試してみてくださいね。
We are hiring !!
エムスリーエンジニアリンググループでは、一緒に働く仲間を募集しています! まずはカジュアル面談から、以下URLよりご応募をお待ちしています。
- 代数的効果、エフェクトハンドラなどとも呼ばれますが、本記事では代数的エフェクトで統一します。↩
- OCaml 5.0.0 - https://ocaml.org/releases/5.0.0↩
- What color is your function? - https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/↩
- 原文を私が意訳したものです。↩
- 端的に表現するなら「継続を考える必要がある」と言えるでしょう。↩
- モナドに詳しくない人は記事中のモナドに関する言及を全て無視しても構いません。↩
- Adequacy for Algebraic Effects - https://homepages.inf.ed.ac.uk/gdp/publications/Op_Sem_Comp_Lam.pdf↩
- Handlers of Algebraic Effects - https://homepages.inf.ed.ac.uk/gdp/publications/Effect_Handlers.pdf↩
- Algebraic Effects for the rest of us - https://overreacted.io/algebraic-effects-for-the-rest-of-us/↩
- Eff, Effekt, Ante など複数の言語が存在しています。↩