リーダーは次のように定義されます。
newtype Reader r a = Reader { runReader :: r -> a }
したがって、実際には、r -> a
追加のカプセル化を備えた型の関数にすぎません。Reader は実際にはモナド内のすべてのアクションに追加の入力を提供するだけなので、これは理にかなっています。
カプセル化を取り除き、関数のみを使用するr -> a
と、モナド関数の型は次のようになります。
return :: a -> (r -> a) -- or: a -> r -> a
(>>=) :: (r -> a) -> (a -> (r -> b)) -> (r -> b) -- or: (r -> a) -> (a -> r -> b) -> r -> b
これを見ると、私たちに何が求められているかが簡単にわかります。型a -> (r -> a)
を見て、これが と同等でa -> r -> a
あることを確認すると、この関数を 2 つの方法で見ることができることがわかります。1 つは、 の引数を取り、a
型の関数を返すことです。もう 1 つは、 と を取り、を返す関数r -> a
として見ることです。次のいずれかのビューを使用して return を実装できます。a
r
a
return a = \r -> a -- or: return a r = a
バインドはよりトリッキーですが、同じロジックが適用されます。私が与えた最初の型シグネチャでは、型の 3 番目r
の型も実際に入力であることはすぐにはわかりませんが、2 番目の型シグネチャはこれを非常に簡単に理解できるようにします。それでは、2 番目の型シグネチャの実装から始めましょう。
(>>=) rToA aAndRToB r = ...
したがって、 type の値、 typer
の関数、r -> a
および type の関数があり、そこa -> r -> b
から type の値を作成することが目標ですb
。唯一b
の入力はa -> r -> b
関数内にあるため、それを使用する必要がありますが、a
フィードする がないため、取得する必要があります。r -> a
があれば、関数はそれを提供できますr
。がありr
ます。これは 3 番目の入力です。したがって、次の値が得られるまで関数を単純に適用できますb
。
(>>=) rToA aAndRToB r = b where
a = rToA r
b = aAndRToB a r
ここで、r
-value をすべてのアクション ( Reader モナドのゴール) に提供すると同時に、a
-value をあるアクションから次のアクション( のゴール) に連鎖させていることがわかります(>>=)
。次のように、最初の型シグネチャを模倣する方法でこれを記述することもできます。
(>>=) rToA aToRToB = \r -> (aToRToB (rToA r)) r
変数の名前を変更すると、Reader のバインドの定義と非常によく似ていますが、 and は使用Reader
しませんrunReader
。
m >>= k = /r -> k (m r) r