モナドは, 型クラス Monad
のインスタンスであるデータ型。具体的には, Maybe
型や Either
, リスト, IO
などなど。
メジャーなプログラミング言語の用語の使い方だと、Haskell のデータ型は具象クラスで、Haskell の型クラスはインタフェイスに近い。
モナドの話はつい抽象的になって、イメージしにくい。具象の例を添える。
Haskell では型クラスも継承できる。型クラス Monad
は Applicative
から, Applicative
は Functor
から派生している。
型クラスを基底クラスのほうから順に見ていく。モナドの具体例は, リストがイメージしやすい。
Functor
Functor
(関手) は、map 演算が適用できることを意味する。その対象としてイメージされるのは、何らかのコンテナだ。Monad
もその派生なので、モナドもコンテナのイメージ。
Functor
の定義は、次のようになっている。
fmap
は、一つ引数を取って何か値を返す関数と functor オブジェクトを引数に取る。新しいfunctor オブジェクトを返す。
Functor
を実装するデータ型は、次の関係を満たさなければならない。id
は引数と同じものを返す関数。
[Identity] fmap id == id [Composition] fmap (f . g) == fmap f . fmap g
具体的には, 例えば, リストの各要素に対して何か変換する関数を適用するものが当たる。
Applicative
型クラス(<*>)
は2項演算子。左辺は, 1引数の関数を Applicative
で包んだもの。右辺は操作対象のデータ。
具象で考えると、例えば、左辺に各要素が関数であるリスト, 右辺に左辺の要素数と同じ数の値を持つリスト。結果は, 右辺のそれぞれの値に左辺のそれぞれの関数を適用した値からなるリスト。
実際に、次のような定義になる。fs
, xs
はリスト。
Applicative
のインスタンス (= 型クラスを実装するデータ型) は、次の関係を満たさなければならない;
[Identity] pure id <*> v = v [Composition] pure (.) <*> u <*> v <*> w = u <*> (v <*> w) [Homomorphism] pure f <*> pure x = pure (f x) [Interchange] u <*> pure y = pure ($ y) <*> u
Monad
Monad
は Applicative
から派生する。
>>=
は bind 演算子と呼ぶ。>>
は then 演算子と呼ぶ。return
は、ほかのプログラミング言語とまったく異なり、関数からの脱出ではない。
(>>=) :: forall a b. m a -> (a -> m b) -> m b
>>=
でつないでいくことができる。
do 記法のなかでの, pat <-
exp は、次と同じ意味;
exp >>=
\ pat -> 次の式
do記法については, Haskell: do記法
(>>) :: forall a b. m a -> m b -> m b
return :: a -> m a
return
に与える式の値はモナドではなく、それをモナド化する。
ほかの型クラスと同様に, Monad
のインスタンスも、いくつかのルールを満たすように実装しなければならない。モナドの場合は有名で, モナド則と呼ばれる。
[Left identity] return a >>= k == k a [Right identity] m >>= return == m [Associativity] m >>= (\x -> (k x >>= h)) == (m >>= k) >>= h
さらに, あまりモナド則には含められないが, 次の関係も満たさなければならない。ただし, m1
は値 x1
として関数を取るモナド。
fail s >>= f == fail s m1 <*> m2 = m1 >>= (x1 -> m2 >>= (x2 -> return (x1 x2))) (*>) = (>>) fmap f xs == xs >>= return . f
利用者としては, データ型がモナド則を満たしていることを前提としてプログラムを組み立てることができるので、難しいことは抜きにしても, 覚えることが減ってよい。
do記法や標準関数も、モナドがモナド則を満たすことを前提としている。
いくつか, モナドであるデータ型を見ていこう。
Maybe
型Maybe
は、正常値と失敗という2種類の値のどちらかを保持する。Preludeで次のように定義されます。
Maybe
は, 次のように, それぞれの型クラスのメソッドを実装し、それぞれ満たさなければならない条件にうまく適合する。
リストは、コンテナ (Functor
のインスタンス) であるだけでなく, モナドでもある。
次のような実装になっており、これも条件をよく満たすことを確認してほしい。