『「作ってかんたんAlgebraic Effects」をRustに移植した』を読んだ
ツイッターでalgebraic effectsについての話題を探していたら以下の記事を見つけた:
Haskellで書かれた「Algebraic Effectsのある処理系の作り方」のRust移植だ。@Nymphiumさんの書かれた元の記事はこちら:
この記事は出た時に拝見したのだが、その頃は私はalgebraic effectsについて何もわかっていなかった。今回両記事を読んで大変面白かったのでつらつらと考えたことを書いていく。
まずAlgebraic Effectsの実装に関しては、型システムなしだと基本的に限定継続の実装ができればほぼ完了な印象だ。限定継続とalgebraic effectsは互いにmacro-expressibleであるという研究があるので当然とも言える。(ついでに言うと同研究ではalgebraic effectsと限定継続はtypeabilityを維持した変換はないという結果も出ている)
限定継続のある(そして静的型付けのない)インタプリタの実装に関しては以前kontlangという処理系を作る話を長々と書いた。これを少し修正すればOCaml版のλeffが作れそう。
algebraic effectを使ったサンプルコードがこちら:
let double = inst () in let h = handler double (val x -> x) ((x, k) -> k (k x)) in with h handle (perform double 3) + 10
限定継続kを2回使っているのがわかる:k (k x)
。これはOCaml 5.0で実装されたeffect handlerではできない。なぜなら限定継続がone-shotだから。
Didier: some people may want to experiment with multishot continuations. Could they do it now, or could they do it on top of existing continuations? Could the runtime provide low-level support, where people are on their own?
Leo: if you add a duplication function for continuations, it's impossible to use correctly and safely. (Optimizing local "let" into stack variables is broken.) I think this is much better done the Oleg way, by writing low-level C code.
Stephen: the semantics issue is, there is no way in general how much you want to copy.
とのこと。
のスレでもいろいろとmulti-shotができなくなったことについてコメントがきている。このスレで挙げられているmulti-shot continuationのライブラリ
にもstackにアロケートするコンパイラ最適化がおかしくなるという話が書いてあった。
それはさておきサンプルコードをちょっといじって
let double = inst () in let h = handler double (val x -> x) ((x, k) -> k (k x)) in with h handle (perform double 3) + (perform double 2)
とすると結果が評価順に依存するコードになるのが面白い。上記のコードの評価結果は左から右に評価していく場合(つまりperform double 3を先に評価する場合)は18、OCamlのように右から左に(+)の引数を評価していく場合は17になる。ぱっと見大して難しいこともしておらず副作用もなさそうなコードで結果が評価順依存になる、というのは限定継続やエフェクトの不思議なところだ。