拡張可能作用 ― モナド変換子に取って代わるもの 6 - shiatsumat/fp-papers GitHub Wiki
6. 関連研究
作用のシステムは、いくつかのアプローチが並行して、時に独立して追求されながら、活発な研究領域となった。主なアプローチとしては、モナド変換子を改良するもの [[27](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference27)]、代数的作用のためのハンドラをエンコードするもの [[1](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference1),[25](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference25)]、型と作用のシステムから始めるもの [[8](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference8),[17](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference17),[32](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference32)] がある。私たちのシステムは、何年か前に EDLS から独立して発展したが、結局上述のアプローチと多くの共通点を持ち、またいくつかの特筆すべき違いを持つに至った。まず、私たちのフレームワークは単独で動作する言語ではない。むしろ、普通の Haskell ライブラリである。一般的な拡張のある Haskell は、上述のものに似た型と作用のシステムを表現できるということが分かった。
モナド変換子とその抽象化 多くの変換子により構成されているモナドの重要な問題の一つは、特定の変換子の層にアクセスするのが難しいということである。暗黙的な持ち上げは不可能かもしれない(例えば State
の層がいくつかあるとき)し、明示的な持ち上げはあまりに苦痛である。シュライファーとオリヴェイラ [[27](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference27)] が、賢明にも層へのアクセスのための新しい便利なメカニズムを使って持ち上げの問題に取り組んだ一方で、スワミーら [[31](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference31)] はどこでどのように持ち上げの演算を行うか推論する ML のためのシステムを提供した。私たちは型で指標付けされた作用を使うことで持ち上げの問題すべてから逃れている。私たちのアプローチはいくつかの State
や Reader
等の作用のどれを実行するかを指し示すという問題も解決している。State
の作用は状態の型によって指標付けされているので、状態の型は自然に、自動的に、余分なメカニズムなしに作用を区別する。同じ型の複数の State
の層は標準的な newtype
のトリックを使うことで扱うことができる。
newtype SInt = D Int deriving (Typeable, Num, Eq)
incr :: (Typeable a, Num a, Member (State a) r) => Eff r a
incr = do x <- get; put (x+1); return (x+1)
doubleIncr = do x <- incr; y <- incr; return (x :: Int, y :: SInt)
run $ runState (runState doubleIncr (0 :: Int)) (5 :: SInt)
-- (((1,6),1),6)
2つの明示的な型注釈 Int
と State
は、doubleIncr
の両方の呼び出しの中の incr
の使い方を区別するのに十分である。私たちは、暗黙的に「変換子スタック」をナビゲートする newtype
のトリックを使える。
代数的作用のためのハンドラ 私たちのアプローチは作用ハンドラ [[1](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference1),[2](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference2),[13](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference13),[21](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference21),[25](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference25),[33](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference33)] についての最近の一致した考えとも密接にかかわっている。これらの作用のハンドラの実装のそれぞれは作用の型付けメカニズムを組み込んでいて、作用の処理の微妙なトレードオフについて決断を下している。
作用ハンドラは他の作用に対する例外ハンドラの一般化である――この洞察はプロットキンとプレトナー [[25](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference25)] によってはじめて持たれた。[図2](拡張可能作用 ― モナド変換子に取って代わるもの 3#fig2) の handle_relay
は現在の Control.Exception
ライブラリ [[20](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference20)] の catch
と実によく似ている――handle_relay
はある型のリクエスト/例外のみを扱い、それ以外のリクエストを自動的に「上流 (upstream)」のハンドラが処理できるよう再び投げるのである。存在型と Typeable
を使ってオープンな和を実装するのも同じである。しかし、私たちのリクエスト(「例外」)は中断されるのではなく再開可能であり、私たちのオープンな和は型付けされている。
フランク [[21](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference21)] のように、私たちのライブラリは浅いハンドラを実装する。それぞれの run???
はハンドルしたい作用だけを、事例ごとに違う方法で選び出す。多くの他の研究 [[1](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference1),[2](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference2),[13](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference13),[33](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference33)] は深いハンドラ に焦点を合わせる。このハンドラでは、ある種類のすべての作用は同じようにハンドルされる。浅いハンドラと比べると、深いハンドラはしばしばより効率的であるがより柔軟性が小さい。
私たちのライブラリは型レベルの制約と型レベルのリストを両方用いることで作用の集合を管理している。カマーら [[13](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference13)] は型クラス制約にのみ頼っている。制約は実際は順序付けされていない集合を表す。しかし制約だけを使うと、Haskell が局所的な型クラスインスタンスをサポートしていないので、すべての作用ハンドラの定義がトップレベルでなければならなくなる。カマーらは、Template Haskell に頼って、型クラスのエンコーディングという不便な作業の大部分を回避し、扱いやすいユーザーインターフェースを提供している。私たちのライブラリは、構文的により軽量であり、特別な構文も必要としない。
もう一つのトレードオフは、同じ作用を持つ複数のインスタンスを許すということである。私たちは同じ計算に State Int
も State Float
もあるのを許可するだけでなく、State Int
のインスタンスがいくつかあることも許可している(後者は容易に禁止できるけれども)。状態の値に対するリクエストあるいは変更は、適切な状態の型を持つ最も近いハンドラによって処理される。Eff
[[1](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference1)] が作用のインスタンスが領域の指標 (index) と結びついている領域ベースのアプローチを取るのに対し、ブラッディーの Idris
のためのライブラリ [[2](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference2)] はユーザーに、それぞれのインスタンスを命名して名前によって参照することを許している。
作用を型レベルで表示する スウィエルストラ、フィリンスキら([[8](拡張可能作用 ― モナド変換子に取って代わるもの 9#reference8)] など)は作用を階層化するための型レベルのシステムを提供している。しかし、そうしたシステムは作用を取り除く能力を欠いている(作用は、システムにおいてハンドルされきったときに取り除かれる)。私たちの継続エンコーディングと和型はフィリンスキのものに似ているが、私たちの和型は作用が取り除かれれば縮む。