Haskell: モナド, 具象データ型, モナド則の嬉しさ

モナドは, 型クラス Monad のインスタンスであるデータ型。具体的には, Maybe 型や Either, リスト, IO などなど。

メジャーなプログラミング言語の用語の使い方だと、Haskell のデータ型は具象クラスで、Haskell の型クラスはインタフェイスに近い。

モナドの話はつい抽象的になって、イメージしにくい。具象の例を添える。

モナドはコンテナ

Haskell では型クラスも継承できる。型クラス Monad は Applicative から, Applicative は Functor から派生している。

型クラスを基底クラスのほうから順に見ていく。モナドの具体例は, リストがイメージしやすい。

Functor

Functor (関手) は、map 演算が適用できることを意味する。その対象としてイメージされるのは、何らかのコンテナだ。Monad もその派生なので、モナドもコンテナのイメージ。

Functor の定義は、次のようになっている。

Haskell
[RAW]
  1. class Functor (f :: * -> *) where
  2. fmap :: (a -> b) -> f a -> f b
  3. (<$) :: a -> f b -> f a
  4. -- デフォルト実装
  5. (<$) = fmap . const

fmap は、一つ引数を取って何か値を返す関数と functor オブジェクトを引数に取る。新しいfunctor オブジェクトを返す。

Functor を実装するデータ型は、次の関係を満たさなければならない。id は引数と同じものを返す関数。

    [Identity]     fmap id      == id
    [Composition]  fmap (f . g) == fmap f . fmap g

具体的には, 例えば, リストの各要素に対して何か変換する関数を適用するものが当たる。

Applicative 型クラス

Haskell
[RAW]
  1. class Functor f => Applicative (f :: * -> *) where -- GHC拡張
  2. pure :: a -> f a -- Lift a value.
  3. (<*>) :: f (a -> b) -> f a -> f b
  4. liftA2 :: (a -> b -> c) -> f a -> f b -> f c
  5. (*>) :: f a -> f b -> f b
  6. (<*) :: f a -> f b -> f a
  7. -- デフォルト実装
  8. (<*>) = liftA2 id
  9. liftA2 f x = (<*>) (fmap f x)
  10. a1 *> a2 = (id <$ a1) <*> a2
  11. (<*) = liftA2 const

(<*>) は2項演算子。左辺は, 1引数の関数を Applicative で包んだもの。右辺は操作対象のデータ。

具象で考えると、例えば、左辺に各要素が関数であるリスト, 右辺に左辺の要素数と同じ数の値を持つリスト。結果は, 右辺のそれぞれの値に左辺のそれぞれの関数を適用した値からなるリスト。

実際に、次のような定義になる。fs, xs はリスト。

Haskell
[RAW]
  1. fs <*> xs = [f x | f <- fs, x <- 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 から派生する。

Haskell
[RAW]
  1. class Applicative m => Monad (m :: * -> *) where
  2. (>>=) :: forall a b. m a -> (a -> m b) -> m b
  3. (>>) :: forall a b. m a -> m b -> m b -- Haskell 2010
  4. return :: a -> m a
  5. fail :: String -> m a -- GHC では MonadFail にて宣言
  6. -- デフォルト実装
  7. m >> k = m >>= \_ -> k
  8. return = pure

>>= は 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
then 演算子は, 左辺の値を捨てて、単に右辺の関数を実行する。
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で次のように定義されます。

Haskell
[RAW]
  1. data Maybe a = Nothing | Just a
  2. deriving (Eq, Ord, Read, Show)

Maybe は, 次のように, それぞれの型クラスのメソッドを実装し、それぞれ満たさなければならない条件にうまく適合する。

Haskell
[RAW]
  1. instance Functor Maybe where
  2. fmap _ Nothing = Nothing
  3. fmap f (Just a) = Just (f a)
  4. instance Applicative Maybe where
  5. pure = Just
  6. Just f <*> m = fmap f m
  7. Nothing <*> _m = Nothing
  8. liftA2 f (Just x) (Just y) = Just (f x y)
  9. liftA2 _ _ _ = Nothing
  10. Just _m1 *> m2 = m2
  11. Nothing *> _m2 = Nothing
  12. instance Monad Maybe where
  13. (Just x) >>= k = k x
  14. Nothing >>= _ = Nothing
  15. (>>) = (*>)
  16. fail _ = Nothing -- GHC では MonadFail のインスタンス

リスト

リストは、コンテナ (Functor のインスタンス) であるだけでなく, モナドでもある。

次のような実装になっており、これも条件をよく満たすことを確認してほしい。

Haskell
[RAW]
  1. instance Functor [] where
  2. fmap = map
  3. instance Applicative [] where
  4. pure x = [x]
  5. fs <*> xs = [f x | f <- fs, x <- xs]
  6. liftA2 f xs ys = [f x y | x <- xs, y <- ys]
  7. xs *> ys = [y | _ <- xs, y <- ys]
  8. instance Monad [] where
  9. xs >>= f = [y | x <- xs, y <- f x]
  10. (>>) = (*>)
  11. fail _ = [] -- GHC では MonadFail のインスタンス

外部リンク

  • All About Monads モナドのすべて Haskell におけるモナドプログラミングの理論と実践に関する包括的ガイド