Algebraic Effects
2024-10
Effect System再考
実装がまちまちすぎるという話
結局のところ、「多様な制御構造を実現できる(がcall/ccやshift/resetより扱いやすい)」という話と、「多様な副作用をそれぞれ分離でき合成もしやすい」という話の2種類に分類できるのかな
https://x.com/yhara/status/1791683646736519557
1. 多様な副作用をトラッキングしたいという話
例えば、この関数を呼んでもIOが起こらないことを保証したいとか
Haskellはこれができる(型がIO xになるため)
フロントエンドの main() を合成関数として副作用を集約する
例えばこの記事ではマーカーが付く(かつ伝播する)ことに重きが置かれていて実装を差し替えたいという話は特にない
2. エフェクト発生時の挙動をカスタマイズしたいという話
テスト時はダミーのDBにアクセスさせたいとか
これも継続を取れる/取れないというバリエーションがある
3. Effect Systemって限定継続に似てるじゃん→これを下回りとして例外やawaitを実装しよう という話
effectがあれば例外が実装できるのはわかりやすい
async/awaitが実装できる、というのはちょっとわかりにくいけどできるらしい
あと、effectが下回りになっていればプログラムはそのままで実装だけ差し替えられる、なるほど
4. ハンドラを動的に差し替えたいという話題?
どのハンドラが使われるかは動的に決まる(極端な話、乱数で2つのうちのどちらかが選ばれるとか)
5. 高階のエフェクト
このように、エフェクトの引数に純粋な値ではなく副作用を持ったコードブロック(プログラム)を取るようなものを高階のエフェクトと呼びます。https://zenn.dev/lanexpr/articles/5bc6f957a7fde4
メモ
Algebraic Effectsとは? 出身は? 使い方は? その特徴とは? 調べてみました! - lilyum ensemble
https://www.eff-lang.org/handlers-tutorial.pdf
実行順序の入れ替え
バックトラック(こういうのcallccの説明例でよく出てくるよね)
handlers-tutorial
Desk言語とAlgebraic Effects and Handlers(代数的エフェクト)
https://homepages.inf.ed.ac.uk/gdp/publications/Effect_Handlers.pdf
Extending Rust's effect system
https://x.com/Kory__3/status/1728604938446819737
Koka
Haskell・Scala界隈ではまた独自の発展が見られる気がするがどっちも詳しくない
heftiaとか 【エフェクトシステムとは一体なんなのか?】なぜ私はHaskellでエフェクトシステムのライブラリを作るに至ったか
同じ型の複数のエフェクトがあるときにどっちに送るかをタグで区別できるとかあってすごい https://zenn.dev/lanexpr/articles/9061f0121f3cf5
Higher-Order Effects Done Right: Heftia Extensible Effectsライブラリの仕組み
こういうのが言語拡張でなくライブラリとして実装できるのがHaskellすごいなって思う
こんなにたくさんライブラリがあるってのはやっぱモナドの合成が辛いってのがモチベーションにあるんだろうか?
A B xではなく(A + B) x にしたい、みたいな
2022-06
Effect実装比較
https://iqkui.com/ja/algebraic-effects-for-the-rest-of-us/
R7RSのraise-continuableに似てるのかなぁ
結局 #アンチネスト構文 の話なのか?
処理系Slackで聞いたりしてもう少し調べた。
Effect:(この文脈では)computational effect、いわゆる副作用全般
ヒープのread/write
I/O (コンソール、ネットワーク、データベース)
例外
Effect system:type→type systemみたいに、effect→effect system。effectの体系。型システムと一体化していることも。
Algebraic effects:プログラミング機構を指す。
call/ccやモナドみたいに、いろんな機能の下回りとして使えるものらしい。
call/ccよりは限定継続に近い
モナドと比べると、合成がかんたんなのがメリット
oneshot, multishotという種類がある
handlerがmultishotだと、ambが実装できたりする。その代わり処理系の実装は大変そう
async/awaitがEffectで書けるってどうやるんだろう
code:rb
def foo1
Http.get(url){|res|
p res
}
end
def foo2
var res = await Http.get(url)
p res
end
def foo3
handle("Http.get"){|url, k|
res = サーバにアクセスしてデータを取得
k(res)
}.run{
res = Http.get(url)
p res
}
end
こうか?
あとは、ファイルを削除するプログラムのテストを書きたいとか
code:rb
def foo
...
File.unlink(@log_path)
...
end
def foo
end
「ファイルを削除する」というeffectを定義する?
「移動する」「リネームする」とかすごくたくさんのeffectが要りそう
この場合、処理をしたあとは継続を単に呼び出すだけで、effectの強力な部分を使っているわけではない?
削除に時間がかかるケースを考えると、削除が終わるまでスケジューラに処理を戻して別の仕事をさせることができるかも
「同期的に削除する」「非同期的に削除する」「なにもしない(テスト用モック)」などの実装が考えられるが、いずれも利用側は同じコードでよい!
cf. 非同期版はawaitが要る、とか
effect handlerにどこまでを許すか
デバッグできなくなりそう
oneshotなら大丈夫かというと、処理順を逆にできたりする
「継続は最後に一度だけ呼ぶ」という制約を付けたら、どうなるだろうか
これだけでもいろいろ便利なことはあるはず
処理系の実装が簡単になったりしないかな
effect handlerはなぜ強いのか
エフェクト発生箇所以降の(部分)継続が暗黙的に取得できるところ
最少の道具で最大の機能を実現したい系の言語にはよさそう
#Shiika にeffect handlerを「取り入れる」意義はあるか(それが可能だとして)
さまざまな制御構造の基礎として使えます→ちょっと強すぎ
副作用部分だけ差し替えることができます
→モックでもできる
同期で書いてあるものを非同期にするとかはさすがにできないけど
非同期(CPS)で書いてあれば、実装を同期にするのは可能
型を見れば副作用の種類と有無がわかる
これはちょっと、いいよね
しかも連鎖するので、間接的に呼ばれるものも含めて副作用がないことが保証される
Rustのunsafeみたいな
メモ
Algebraic EffectsとExtensible Effectsの違いってなんや? 関係あんの? - lilyum ensemble
Rubyでもalgebraic effectsがしたい! - lilyum ensemble
0から知った気になるAlgebraic Effects - lilyum ensemble
Algebraic Effectsとは? 出身は? 使い方は? その特徴とは? 調べてみました! - lilyum ensemble
performはawaitと似ている
performは実装を差し替えられるが、awaitは差し替えられないというところが違う
(継続を最後に一度だけ呼び出す場合に限ると)performは「プログラムのこっからここまで」を穴にしている
それは普通のメソッド呼び出しも同じでは?
RubyやC#のモック:「このメソッドをこの引数で呼び出す」だけが固定されている
レシーバを差し替えることで挙動を変えられる
となるとやっぱ継続が渡されることが一番の違いなんだよな
既存実装はどう言っているか
https://dry-rb.org/gems/dry-effects/0.1/
「グローバル変数っぽいがグローバルではない」
https://github.com/digital-fabric/affect
依存関係の逆転とかDI
テスタビリティ
意図と実装の分離
これさあAffect.getsが何するのかわからんよね
追うのが大変そうじゃないかなって思った
型があれば戻り値の型くらいは分かるけど
Why PLs Should Have Effect Handlers - Quil's Fluffy World
現在時刻に応じてメッセージを表示する→「時刻の取得」と「メッセージを表示」が副作用
ベタ書きだとテストに困ったり、別の環境(eg. wasm)に移植するとき困る
従来ならinterface IOとかで差し替え可能にしていた場面
「dry-runが簡単にできる」
Streams and Subscriptions | Effection
ReactのuseEffect https://ja.reactjs.org/docs/hooks-effect.html
「副作用を分離する」ではあるけど、algebraic handlerぽくはないな
Promiseをthrowするやつって何だっけ?Suspenseか。
Concurrent Mode時代のReact設計論 (1) Concurrent Modeにおける非同期処理 - Qiita
コンポーネントからPromiseをthrowすることで「まだレンダリングできない」を表現
また、そのPromiseを解決することで「レンダリングが可能になった」を表現
値を取得する関数がPromise<T>からただのT(+ throw)になるので、あたかも既にある値を取得するように見える(実際は無かったらReactが値が取得できるまで待って再実行するので、値があるときだけ残りの処理が行われる)
Concurrent Mode時代のReact設計論 (2) useTransitionを活用する - Qiita
Suspenseだけだと、ボタン押す→データ受信待ち→結果描画となり、見た目が悪い
このためのuseTransition
Concurrent Mode時代のReact設計論 (3) SuspenseやuseTransitionが何を解決するか - Qiita
また、細かいことをいえば、「fetchUsers()の結果が帰ってきたらsetStateする」という処理は命令的な書き方であり、宣言的にUIを記述する流れに逆行しています。
vs. "ロードできた部分から順次表示していくというパターン"
画面Bがデータを待つという部分も、Suspenseの機能およびFetcherによって、手続き的な部分がReactの内部に隠蔽され、宣言的な書き方ができています。
Concurrent Mode時代のReact設計論 (4) コンポーネント設計にサスペンドを組み込む - Qiita
Suspenseをネストさせることで、データの到着順と表示状態を制御できる
Concurrent Mode時代のReact設計論 (6) Concurrent Modeと副作用 - Qiita
逆から見れば、Reactに管理された世界は純粋な(副作用のない)世界です。例えば関数コンポーネントは副作用を発生させないことが期待されています。つまり、関数を呼び出しても(Reactの与り知らぬところで)何も起きないということです。この仮定があるからこそ、Reactは関数コンポーネントを好き勝手に呼び出すことができます。
Facebook Relayについてまとめ - Qiita (脱線)