直感
大まかな直感では、モナドは特定の種類のコンテナー(Functor
)であり、2つの操作を使用できます。return
単一の要素をコンテナに取り込むラッピング操作。join
コンテナーのコンテナーを単一のコンテナーにマージする操作。
return :: Monad m => a -> m a
join :: Monad m => m (m a) -> m a
したがって、モナドの場合、多分あなたは持っています:
return :: a -> Maybe a
return x = Just x
join :: Maybe (Maybe a) -> Maybe a
join (Just (Just x) = Just x
join (Just Nothing) = Nothing
join Nothing = Nothing
同様に、モナド[]の場合、これらの操作は次のように定義されます。
return :: a -> [a]
return x = [x]
join :: [[a]] -> [a]
join xs = concat xs
モナドの標準的な数学的定義は、これらの戻り演算子と結合演算子に基づいています。ただし、Haskellでは、クラスMonadの定義により、結合の代わりにバインド演算子が使用されます。
Haskellのモナド
関数型プログラミング言語では、これらの特別なコンテナーは通常、効果的な計算を示すために使用されます。タイプMaybe a
は、成功する場合と成功しない場合がある計算を表し、タイプ[a]
は非決定論的な計算を表します。a->m b
特に、効果のある関数、つまりいくつかの型を持つ関数に関心がありますMonad m
。そして、それらを構成できる必要があります。これは、モナディックコンポジションまたはバインド演算子のいずれかを使用して実行できます。
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Haskellでは後者が標準的なものです。その型はアプリケーション演算子の型と非常に似ていることに注意してください(ただし、引数が反転しています)。
(>>=) :: Monad m => m a -> (a -> m b) -> m b
flip ($) :: a -> (a -> b) -> b
効果的な関数とタイプの値を返すf :: a -> m b
計算を受け取り、アプリケーションを実行します。では、モナドでこれをどのように行うのでしょうか?コンテナ()はマッピングできます。この場合、結果は計算内の計算になり、それをフラット化できます。mx :: m a
a
mx >>= f
Functors
fmap f mx :: m (m b)
join (fmap f mx) :: m b
だから私たちは持っています:
(mx >>= f) = join (fmap f mx) :: m b
これが実際に機能することを確認するには、リスト(非決定論的関数)を使用した簡単な例を検討してください。可能な結果のリストと非決定mx = [1,2,3]
論的関数があるとしますf x = [x-1, x*2]
。計算mx >>= f
するには、まずmxをfにマッピングしてから、結果をマージします。
fmap f mx = [[0,2],[1,4],[2,6]]
join [[0,2],[1,4],[2,6]] = [0,2,1,4,2,6]
Haskellではバインド演算子(>>=)
がより重要でjoin
あるため、後者の効率上の理由から前者から定義され、その逆ではありません。
join mx = mx >>= id
また、joinおよびfmapで定義されているバインド演算子を使用して、マッピング操作を定義することもできます。このため、モナドはクラスFunctorのインスタンスである必要はありません。fmapと同等の操作liftM
は、Monadライブラリで呼び出されます。
liftM f mx = mx >>= \x-> return (f x)
したがって、モナドの実際の定義は次のようになります。
return :: a -> Maybe a
return x = Just x
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>= f = Nothing
Just x >>= f = f x
そしてモナドの場合[]:
return :: a -> [a]
return x = [x]
(>>=) :: [a] -> (a -> [b]) -> [b]
xs >>= f = concat (map f xs)
= concatMap f xs -- same as above but more efficient
独自のモナドを設計する場合、直接定義するのではなく(>>=)
、問題を部分に分割して、構造をマッピングおよび結合する方法を理解する方が簡単な場合があります。マップと結合があると、必要な法則を満たしているという意味で、モナドが明確に定義されていることを確認するのにも役立ちます。
モナド法
モナドはファンクターである必要があるため、マッピング操作は次の条件を満たす必要があります。
fmap id = id
fmap g . fmap f = fmap (g . f)
復帰および参加の法律は次のとおりです。
join . return = id
join . fmap return = id
join . join = join . fmap join
最初の2つの法則は、マージによってラッピングが元に戻されることを指定しています。コンテナを別のコンテナでラップすると、joinによって元のコンテナが返されます。ラッピング操作を使用してコンテナーの内容をマップすると、再度結合すると、最初に持っていたものが返されます。最後の法則は、結合の結合法則です。コンテナが3層ある場合は、内側または外側からマージしても同じ結果が得られます。
ここでも、joinとfmapの代わりにbindを使用できます。取得する法則は少なくなりますが、(おそらく)より複雑になります。
return a >>= f = f a
m >>= return = m
(m >>= f) >>= g = m >>= (\x -> f x >>= g)