インデックス付きモナドとは何ですか?


98

とは インデックス付きモナドとですか?このモナドの動機は?

私はそれが副作用を追跡するのに役立つことを読みました。しかし、型シグネチャとドキュメンテーションは私をどこにも導きません。

副作用の追跡に役立つ例(または他の有効な例)は何ですか?

回答:


123

いつものように、人々が使用する用語は完全に一貫しているわけではありません。モナドに触発されたが厳密に言えば、そうではない概念はさまざまです。「インデックス付きモナド」という用語は、そのような概念の1つを特徴付けるために使用される用語の1つ(「モナディッシュ」と「パラメータ化モナド」(Atkeyの名前))です。(もし興味があれば、そのような別の概念は、勝又の「パラメトリック効果モナド」であり、モノイドによってインデックスが付けられ、戻り値はニュートラルにインデックスされ、バインドはそのインデックスに蓄積されます。)

まずは種類を確認しましょう。

IxMonad (m :: state -> state -> * -> *)

つまり、「計算」のタイプ(または「アクション」、必要に応じて「計算」のみを使用)は、次のようになります。

m before after value

どこbefore, after :: statevalue :: *。このアイデアは、予測可能な状態の概念を持つ外部システムと安全に対話するための手段を取り込むことです。計算のタイプは、実行する必要がある状態before、実行する状態after、および(通常のモナドの場合と同様に*)どのタイプのタイプかを示しますvalue計算によって生成さます。

通常の小片は*、モナドの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

そして、相変わらず、法律はそれを正確に保証しireturnicomp私たちにカテゴリーを与えます

      ireturn `icomp` g = g
      f `icomp` ireturn = f
(f `icomp` g) `icomp` h = f `icomp` (g `icomp` h)

または、コメディの偽のC / Java /何でも

      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

そこから他のものはで組み立てられるireturnibind。今、私は書くことができます(借用- do記法)

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

実質的には、Kleisliの原始的な矢印とは何か(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つの理由から、そうではありません。一つは、それがあるため、より多くのモナドが何であるかのように見えるというモナドが、オーバー(state -> *)ではなく*。2つ目は、クライスリ矢印のタイプを見ると、

a :-> m b   =   forall state. a state -> m b state

Good Old Hoare Logicと同様に、前提条件 aと事後条件を使用して計算のタイプを取得しますb。プログラムロジックのアサーションは、カリーハワード対応を越えてHaskell型になるまでに半世紀もかかりませんでした。タイプは、returnIx「何もしないだけで、成立する事後条件を達成できる」と言います。これは、「スキップ」のHoare論理規則です。対応する構成は、「;」のHoare論理規則です。

最後に、のタイプを調べ、bindIxすべての数量詞を入力します。

bindIx :: forall i. m a i -> (forall j. a j -> m b j) -> m b i

これらforallの極性は逆です。初期状態iと、i事後条件付きで開始できる計算を選択しますa。世界はj好きな中間状態を選択しますが、ポストコンディションbが成り立つという証拠を提供する必要があり、そのようなどの状態からも、持ち続けることができbます。したがって、順番に、状態bから条件を達成できiます。「後」の状態を把握することで、予測できない計算をモデル化できます。

どちらIxMonadMonadIx便利です。どちらも、状態の変化に関するインタラクティブな計算の有効性を、それぞれ予測可能および予測不可能にモデル化します。予測可能性は、それを手に入れることができる場合に価値がありますが、予測不可能性は、時には現実の事実です。うまくいけば、この回答はインデックス付きモナドが何であるかをいくつか示し、それらがいつ有用になり始めるか、いつ停止するかを予測します。


1
True/ False値を型引数としてどのように渡すことができますDVDDriveか?それはいくつかの拡張機能ですか、またはブール値が実際にここに入力されていますか?
ベルギ

8
@Bergiブール値は型レベルで存在するように「持ち上げられ」ました。これはHaskellでDataKinds拡張機能を使用して可能であり、依存型付き言語でも可能です...まあ、それは一種のことです。
J.アブラハムソン

MonadIx例を挙げて、について少し詳しく説明していただけますか?理論的根拠の方が良いのか、それとも実用的なアプリケーションの方が良いのか?
クリスチャンコンクル2015

2
@ChristianConkle私はそれがひどく役に立たないことを理解しています。しかし、あなたは本当にまったく別の質問であるものを提起します。局所的に、MonadIxが「より良い」と私が言うとき、私は予測できない環境との相互作用をモデル化することを意味します。あなたのDVDドライブがDVDを吐き出すことが許可されている場合、それらを挿入しようとするのは好きではありません。いくつかの実際的な状況は、それと同様に悪い振る舞いをしています。他のものはより予測可能性があります(つまり、操作が失敗しないというわけではなく、どの状態で継続が始まるかを言うことができます)。その場合、IxMonadの方が扱いやすいです。
pigworker 2015

1
答えのdo表記を「借用」するとき、それが実際にRebindableSyntax拡張子付きの有効な構文であると言うと役立つ場合があります。前述のように、他の必要な拡張子の言及は、いいだろうDataKinds
ギガバイト

46

私が知っているインデックス付きモナドを定義するには、少なくとも3つの方法があります。

私はこれらのオプションをXのインデックス付きモナドと呼びます。Xは、コンピューターサイエンティストのBob Atkey、Conor McBride、およびDominic Orchardの範囲です。これらの構造の一部は、カテゴリ理論を通じて、はるかに長い輝かしい歴史とより良い解釈を持っていますが、最初にこれらの名前に関連付けられていることを知り、この答え難解になりすぎないようにしています

Atkey

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 -> *)ます。モナドをで構築するのではなく、で構築します。

興味深いことに、賢明なデータ型を使用することにより、実際にAtkeyのインデックス付きモナドのスタイルをMcBrideのスタイルから回復できます。

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の場合にのみ呼び出すことができ、を注意深く読むとibindAtkeyと同じように戻すことができますibind。これらの(:=)データ構造を渡す必要がありますが、Atkeyプレゼンテーションの力を回復します。

一方、Atkeyのプレゼンテーションは、McBrideのバージョンのすべての使用を回復するほど強力ではありません。力は厳しく獲得されました。

もう1つの良い点は、マクブライドのインデックス付きモナドが明らかにモナドであり、それが別のファンクターカテゴリのモナドにすぎないことです。これは、からへのファンクタのカテゴリではなく、から(k -> *)へのファンクタのカテゴリで内部(k -> *)ファンクタ*に対して機能し*ます。

楽しい演習では、インデックス付きコモナードの McBrideからAtkeyへの変換方法を考え出します。私はマクブライドの論文の「アットキー」構造に「At」というデータタイプを個人的に使用しています。私は実際にICFP 2013でボブ・アットキーに近づき、彼を裏返しにして彼を「コート」にしたと述べました。彼は目に見えて邪魔をしているようだった。頭の中でラインがうまく流れました。=)

オーチャード

最後に、「インデックス付きモナド」という名前のあまり一般的ではない3番目の主張者は、ドミニクオーチャードによるもので、代わりにタイプレベルのモノイドを使用してインデックスをつぶします。構造の詳細を確認するのではなく、単にこの話にリンクします。

https://github.com/dorchard/effect-monad/blob/master/docs/ixmonad-fita14.pdf


1
OrchardのモナドはAtkeyのモナドに相当しますが、内相モノイドを取得することで前者から後者に移動し、状態遷移でモノイドのアペンドをCPSエンコードすることで後退できます。
アンドラス・コバックス

それは私にはもっともらしく聞こえます。
エドワードKMETT 2015

とはいえ、ICFP 2013で彼が私に言ったことに基づいて、オーチャードは彼のタイプファミリーがいくつかの矢印が接続できない任意のカテゴリではなく、実際のモノイドのように振る舞うことを意図していたので、ストーリーにもっとあるかもしれませんそれよりも、Atkeyの構造により、いくつかのKleisliアクションが他のアクションと接続するのを簡単に制限できるようになります。
エドワードKMETT 2015

2
「注意深い読み」をさらに詳しく説明するにはibind、型エイリアスを紹介しAtkey m i j a = m (a := j) iます。これを使用してm、我々が検索Atkeyの定義が回復における2つのシグネチャ:ireturnAtkin :: a -> m (a := i) iibindAtkin :: m (a := j) i -> (a -> m (b := k) j) -> m (b := k) i。最初のものは、合成によって得られますireturn . V。2番目の方法は、(1)forall j. (a := j) j -> m (b := k) jパターンマッチングによって関数を作成し、復元aしたものをの2番目の引数に渡しますibindAtkin
WorldSEnder

23

簡単なシナリオとして、状態モナドがあると仮定します。状態タイプは複雑で大きなものですが、これらの状態はすべて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

第2のインデックスが原因タイプエラーがトリガされblueToRedRed)の最初のインデックスと異なりますblueToBlueBlue)。

別の例として、インデックス付きモナドを使用すると、状態モナドがその状態のタイプを変更できるようにすることができます。たとえば、

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タイプでファイルに関連しないすべての関数を複製する必要があります。


18

インデックス付きモナドは、たとえば、状態モナドのような特定のモナドではなく、追加の型パラメーターを持つモナド概念の一種の一般化です。

「標準」モナディック値が型を持つのに対し、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と状態があり、計算の終わりにはタイプがありjます。

通常のモナドに対してインデックス付きモナドを使用する必要はめったにありませんが、場合によっては、より厳密な静的保証をエンコードするために使用できます。


5

依存型(agdaなど)でのインデックスの使用方法を確認することが重要な場合があります。これは、一般的にインデックス作成がどのように役立つかを説明し、この経験をモナドに変換できます。

索引付けにより、タイプの特定のインスタンス間の関係を確立できます。次に、いくつかの値について推論して、その関係が成り立つかどうかを確認できます。

たとえば(agdaで)いくつかの自然数がと関連していることを指定でき_<_、型はそれらがどの数であるかを示します。次にm < n、関数が正しく機能するため、一部の関数にの目撃情報を与えることを要求できます。そのような目撃情報を提供しないと、プログラムはコンパイルされません。

別の例として、選択した言語に対する十分な忍耐力とコンパイラーのサポートが与えられた場合、関数が特定のリストがソートされていると想定するようにエンコードできます。

インデックス付きモナドでは、依存型システムが行うことの一部をエンコードして、副作用をより正確に管理できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.