インデックス付きモナド とは何ですか?また、このモナドの動機は何ですか?
副作用を追跡するのに役立つことを読みました。しかし、型の署名とドキュメントは私をどこにも導きません。
副作用の追跡に役立つ例(または他の有効な例)は何でしょうか?
いつものように、人々が使用する用語は完全に一貫しているわけではありません。モナドにインスパイアされたものの、厳密に言えば非常に多くの概念があります。 「インデックス付きモナド」という用語は、そのような概念の特徴を表すために使用される用語の1つ(「モナディッシュ」および「パラメータ化モナド」(それらのAtkeyの名前)を含む)です。 (もしあなたが興味を持っているなら、このような別の概念は、勝者の「パラメトリック効果モナド」であり、モノイドによってインデックスが付けられ、リターンはニュートラルにインデックス付けされ、そのインデックスにバインドが蓄積されます。)
まず、種類を確認しましょう。
IxMonad (m :: state -> state -> * -> *)
つまり、「計算」(または「アクション」、必要に応じて、ただし「計算」に固執する)のタイプは、次のようになります。
m before after value
ここで、before, after :: state
およびvalue :: *
。アイデアは、いくつかの予測可能状態の概念を持つ外部システムと安全に対話する手段をキャプチャすることです。計算のタイプは、実行する状態がbefore
でなければならないこと、状態が実行するafter
であること、および(*
上の通常のモナドのように)計算が生成するvalue
sのタイプを示します。
通常の小片は、*
がモナドのように、state
がドミノをプレイするのと同じです。
ireturn :: a -> m i i a -- returning a pure value preserves state
ibind :: m i j a -> -- we can go from i to j and get an a, thence
(a -> m j k b) -- we can go from j to k and get a b, therefore
-> m i k b -- we can indeed go from i to k and get a b
このようにして生成された「Kleisli arrow」(計算をもたらす関数)の概念は
a -> m i j b -- values a in, b out; state transition i to j
そして、私たちは構図を得る
icomp :: IxMonad m => (b -> m j k c) -> (a -> m i j b) -> a -> m i k c
icomp f g = \ a -> ibind (g a) f
そして、いつものように、法律はireturn
とicomp
がカテゴリを与えることを厳密に保証しています
ireturn `icomp` g = g
f `icomp` ireturn = f
(f `icomp` g) `icomp` h = f `icomp` (g `icomp` h)
または、コメディーの偽のC/Java/whateverで、
g(); skip = g()
skip; f() = f()
{g(); h()}; f() = h(); {g(); f()}
なぜわざわざ?相互作用の「ルール」をモデル化します。たとえば、ドライブにDVDがない場合、DVDを取り出すことはできません。また、すでにDVDが入っている場合、DVDをドライブに入れることはできません。そう
data DVDDrive :: Bool -> Bool -> * -> * where -- Bool is "drive full?"
DReturn :: a -> DVDDrive i i a
DInsert :: DVD -> -- you have a DVD
DVDDrive True k a -> -- you know how to continue full
DVDDrive False k a -- so you can insert from empty
DEject :: (DVD -> -- once you receive a DVD
DVDDrive False k a) -> -- you know how to continue empty
DVDDrive True k a -- so you can eject when full
instance IxMonad DVDDrive where -- put these methods where they need to go
ireturn = DReturn -- so this goes somewhere else
ibind (DReturn a) k = k a
ibind (DInsert dvd j) k = DInsert dvd (ibind j k)
ibind (DEject j) k = DEject j $ \ dvd -> ibind (j dvd) k
これを配置すると、「プリミティブ」コマンドを定義できます
dInsert :: DVD -> DVDDrive False True ()
dInsert dvd = DInsert dvd $ DReturn ()
dEject :: DVDrive True False DVD
dEject = DEject $ \ dvd -> DReturn dvd
ここから、ireturn
およびibind
で他のものがアセンブルされます。今、私は書くことができます(do
- notationを借りる)
discSwap :: DVD -> DVDDrive True True DVD
discSwap dvd = do dvd' <- dEject; dInsert dvd ; ireturn dvd'
物理的に不可能ではない
discSwap :: DVD -> DVDDrive True True DVD
discSwap dvd = do dInsert dvd; dEject -- ouch!
あるいは、プリミティブコマンドを直接定義できます
data DVDCommand :: Bool -> Bool -> * -> * where
InsertC :: DVD -> DVDCommand False True ()
EjectC :: DVDCommand True False DVD
次に、汎用テンプレートをインスタンス化します
data CommandIxMonad :: (state -> state -> * -> *) ->
state -> state -> * -> * where
CReturn :: a -> CommandIxMonad c i i a
(:?) :: c i j a -> (a -> CommandIxMonad c j k b) ->
CommandIxMonad c i k b
instance IxMonad (CommandIxMonad c) where
ireturn = CReturn
ibind (CReturn a) k = k a
ibind (c :? j) k = c :? \ a -> ibind (j a) k
事実上、私たちは原始的なクライスリ矢印が何であるか(1つの「ドミノ」とは何か)を述べ、それらの上に「計算シーケンス」の適切な概念を構築しました。
すべてのインデックス付きモナドm
で、「変化しない対角線」m i i
はモナドですが、一般的に、m i j
はモナドではありません。さらに、値はインデックス付けされませんが、計算はインデックス付けされます。そのため、インデックス付けされたモナドは、他のカテゴリに対してインスタンス化されたモナドの通常のアイデアではありません。
さて、クライスリの矢印のタイプをもう一度見てください
a -> m i j b
開始するには状態i
でなければならないことがわかっており、継続は状態j
から始まると予測しています。このシステムについて多くのことを知っています!これは危険な操作ではありません!ドライブにDVDを入れると、DVDが入ります! dvdドライブは、各コマンドの後の状態について何も発言しません。
しかし、世界と対話するとき、それは一般的に真実ではありません。場合によっては、コントロールを与えて、世界に好きなことをさせる必要があるかもしれません。たとえば、サーバーの場合は、クライアントに選択肢を提供できますが、セッション状態はクライアントが選択したものに依存します。サーバーの「オファー選択」操作は結果の状態を決定しませんが、サーバーはとにかく続行できるはずです。上記の意味では「プリミティブコマンド」ではないため、インデックス付きモナドは、予測不可能シナリオをモデル化するのに適したツールではありません。
より良いツールは何ですか?
type f :-> g = forall state. f state -> g state
class MonadIx (m :: (state -> *) -> (state -> *)) where
returnIx :: x :-> m x
flipBindIx :: (a :-> m b) -> (m a :-> m b) -- tidier than bindIx
怖いビスケット?そうではありませんが、2つの理由があります。 1つは、モナドと似ているように見えます。これは、isモナドですが、(state -> *)
ではなく*
の上にあるためです。 2つ目は、Kleisliの矢印のタイプを見ると、
a :-> m b = forall state. a state -> m b state
good Old Hoare Logicと同じように、前提条件a
および事後条件b
を使用して、計算のタイプを取得します。プログラムロジックのアサーションは、カリーハワード通信を越えてHaskell型になるまでに半世紀もかかりました。 returnIx
のタイプは、「何もしないだけで、保持する任意の事後条件を達成できる」と言います。これは、「スキップ」のHoare Logicルールです。対応する構成は、「;」のHoare Logicルールです。
最後に、bindIx
のタイプを見て、すべての数量詞を入れます。
bindIx :: forall i. m a i -> (forall j. a j -> m b j) -> m b i
これらのforall
sの極性は逆です。初期状態i
、および事後条件i
でa
から開始できる計算を選択します。世界は好きな中間状態j
を選択しますが、事後条件b
が成立するという証拠を提供する必要があり、そのような状態からb
を成立させることができます。したがって、順番に、状態b
から条件i
を達成できます。 「後」の状態を把握することで、予測不能計算をモデル化できます。
IxMonad
とMonadIx
の両方が便利です。両方とも、予測可能な状態と予測不可能な状態の変化に関するインタラクティブな計算の有効性をそれぞれモデル化します。予測可能性は、入手できれば貴重ですが、予測不可能性は時々現実です。願わくば、この回答がインデックス付きモナドが何であるかを示し、有用になり始めるときと停止するときの両方を予測することを願っています。
私が知っているインデックス付きモナドを定義するには、少なくとも3つの方法があります。
これらのオプションをXのインデックス付きモナドと呼びます。Xはコンピューター科学者のボブ・アトキー、コナー・マクブライド、ドミニク・オーチャードの範囲です。私がそれらについて考える傾向があります。これらの構造の一部には、カテゴリ理論によるはるかに長い歴史とより良い解釈がありますが、これらの名前に関連付けられていることを最初に知り、この答えがを取得しないようにしています難解。
Bob Atkeyのインデックス付きモナドのスタイルは、モナドのインデックスを処理するために2つの追加パラメータを使用することです。
それにより、他の回答で人々が投げかけた定義が得られます:
class IMonad m where
ireturn :: a -> m i i a
ibind :: m i j a -> (a -> m j k b) -> m i k b
Atkeyのようにインデックス付きのコマンドも定義できます。私は実際にこれらの lens
コードベース内 から多くのマイレージを得ています。
インデックス付きモナドの次の形式は、コナーマクブライドの彼の論文からの定義です "Kleisli Arrows of Outrageous Fortune" 。代わりに、インデックスに単一のパラメーターを使用します。これにより、インデックス付きモナド定義はかなり巧妙な形になります。
次のようにパラメトリック性を使用して自然な変換を定義する場合
type a ~> b = forall i. a i -> b i
マクブライドの定義を
class IMonad m where
ireturn :: a ~> m a
ibind :: (a ~> m b) -> (m a ~> m b)
これはAtkeyのものとはかなり異なりますが、(m :: * -> *)
でモナドを構築する代わりに、(m :: (k -> *) -> (k -> *)
で構築します。
興味深いことに、McBrideが彼の独特のスタイルで「キーで」と読むべきだと言う賢いデータ型を使用することで、McBrideからAtkeyのインデックス付きモナドのスタイルを実際に回復できます。
data (:=) :: a i j where
V :: a -> (a := i) i
今、あなたはそれを解決することができます
ireturn :: IMonad m => (a := j) ~> m (a := j)
に展開します
ireturn :: IMonad m => (a := j) i -> m (a := j) i
j = iの場合にのみ呼び出すことができ、ibind
を注意深く読み取ると、Atkeyのibind
と同じように戻ります。これらの(:=)データ構造を渡す必要がありますが、Atkeyプレゼンテーションの力を回復します。
一方、AtkeyプレゼンテーションはMcBrideのバージョンのすべての使用を回復するほど強力ではありません。権力が厳しく獲得されました。
もう1つの素晴らしい点は、McBrideのインデックス付きモナドは明らかにモナドであり、異なるファンクターカテゴリのモナドにすぎないことです。 (k -> *)
から(k -> *)
のファンクターのカテゴリーではなく、*
から*
のファンクターのカテゴリーでエンドファンクターに作用します。
楽しい練習は、インデックス付きcomonadsに対してMcBrideからAtkeyへの変換を行う方法を見つけ出すことです。私は個人的に、マクブライドの論文の「at key」構造にデータ型「At」を使用しています。 ICFP 2013で実際にボブ・アトキーまで歩いて行き、彼を裏返しにして「コート」にしたと言いました。彼は明らかに邪魔されたように見えた。私の頭の中で線はより良くなりました。 =)
最後に、「インデックス付きモナド」という名前の、あまり一般的ではないが参照される3番目の要求者は、ドミニクオーチャードによるものです。構成の詳細を説明するのではなく、この講演にリンクします。
簡単なシナリオとして、状態モナドがあると仮定します。状態タイプは複雑で大きなものですが、これらの状態はすべて赤と青の状態の2つのセットに分割できます。このモナドの一部の操作は、現在の状態が青の状態である場合にのみ意味を持ちます。これらのうち、いくつかは状態を青のままにする(blueToBlue
)が、他は状態を赤にする(blueToRed
)。通常のモナドでは、次のように書くことができます
_blueToRed :: State S ()
blueToBlue :: State S ()
foo :: State S ()
foo = do blueToRed
blueToBlue
_
2番目のアクションは青の状態を予期しているため、ランタイムエラーが発生します。これを静的に防止したいと思います。インデックス付きモナドはこの目標を達成します。
_data Red
data Blue
-- assume a new indexed State monad
blueToRed :: State S Blue Red ()
blueToBlue :: State S Blue Blue ()
foo :: State S ?? ?? ()
foo = blueToRed `ibind` \_ ->
blueToBlue -- type error
_
blueToRed
の2番目のインデックス(Red
)がblueToBlue
の最初のインデックス(Blue
)と異なるため、型エラーがトリガーされます。
別の例として、インデックス付きモナドを使用すると、状態モナドがその状態のタイプを変更できるようにすることができます。あなたが持つことができる
_data State old new a = State (old -> (new, a))
_
上記を使用して、静的に型付けされた異種スタックである状態を構築できます。操作にはタイプがあります
_Push :: a -> State old (a,old) ()
pop :: State (a,new) new a
_
別の例として、ファイルへのアクセスを許可しない制限付きのIOモナドが必要であると想定します。
_openFile :: IO any FilesAccessed ()
newIORef :: a -> IO any any (IORef a)
-- no operation of type :: IO any NoAccess _
_
このようにして、タイプIO ... NoAccess ()
を持つアクションは、ファイルアクセスフリーであることが静的に保証されます。代わりに、タイプIO ... FilesAccessed ()
のアクションはファイルにアクセスできます。インデックス付きモナドを使用すると、制限付きIO用に別の型を作成する必要がなくなり、両方のIO型でファイルに関連しないすべての関数を複製する必要があります。
インデックス付きモナドは、たとえば状態モナドのような特定のモナドではなく、追加の型パラメーターを使用したモナド概念の一種の一般化です。
一方、「標準」モナド値のタイプはMonad m => m a
インデックス付きモナドの値はIndexedMonad m => m i j a
ここで、i
とj
はインデックスタイプであるため、i
はモナド計算の開始時のインデックスのタイプであり、j
は終了時のインデックスのタイプです。計算の。ある意味では、i
は入力タイプの一種、j
は出力タイプと考えることができます。
State
を例として使用すると、ステートフル計算State s a
は、計算中にs
型の状態を維持し、a
型の結果を返します。インデックス付きバージョン、IndexedState i j a
は、ステートフル計算であり、計算中に状態が異なるタイプに変化する可能性があります。初期状態のタイプはi
およびstateであり、計算の終了のタイプはj
です。
通常のモナド上でインデックス付きモナドを使用する必要はめったにありませんが、場合によってはより厳密な静的保証をエンコードするために使用できます。
依存型(agdaなど)でのインデックスの使用方法を確認することが重要な場合があります。これにより、インデックス作成が一般的にどのように役立つかを説明し、この経験をモナドに変換できます。
インデックス付けにより、型の特定のインスタンス間の関係を確立できます。次に、いくつかの値について推論して、その関係が成立するかどうかを確立できます。
たとえば、(agdaで)いくつかの自然数が_<_
と関連していることを指定でき、タイプはそれらがどの数であるかを示します。次に、一部の関数にm < n
という監視を与えるように要求できます。これは、その関数が正しく動作するためです。そのような監視を提供しないと、プログラムはコンパイルされません。
別の例として、選択した言語に対する十分な忍耐力とコンパイラのサポートがあれば、特定のリストがソートされていると関数が想定するようにエンコードできます。
インデックス付きモナドを使用すると、依存型システムが行うことの一部をエンコードして、副作用をより正確に管理できます。