このブログ記事では、Haskellの非常に重要な型クラス、Monadを紹介します。

振り返り
前回の2つの記事では、Functor
とApplicative
について取り上げました。Functor
は、型コンストラクタ内の値に対してfmap
を使って関数を実行でき、
Applicative
はpure
、<$>
、および<*>
を使って一つのファンクター内の関数を別のファンクター内の値に対して実行できます。
Functor
とApplicative Functor
について自信がない場合は、Road to Haskeller #13と#14の記事を確認してください。
Monad
さて、いよいよMonad
について話す準備が整いました。Monad
はApplicative
の自然な進化形であり、型aから型bを内包するApplicative
を
導出する関数を型aを内包するApplicative
に使用できるようにしたものです。言葉ではよくわからないと思うので早速、Monad
の定義を見てみましょう。
class Applicative m => Monad (m :: * -> *) where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
{-# MINIMAL (>>=) #-}
MINIMAL
からわかるように、Applicative
がMonad
になるためには(>>=)
またはbindを定義するだけで済みます。
bind関数は型aの値を持つApplicative
と、型aの入力を取り、型bのApplicative
を出力する関数を渡して、型bの値を持つApplicative
を得る関数です。
これはFunctor
やApplicative
では不可能でした。なぜなら、Functor
は(a -> b)
の関数しか受け付けず、
Applicative
はファンクター内の(a -> b)
しか受け付けないからです(bindは (a -> m b)
を使用可能)。
Monad
をより理解するために、いくつかの例を見てみましょう。
Maybe
Maybe
は次のように定義されたMonad
のインスタンスです。
instance Monad Maybe where
return x = Just x
Nothing >>= f = Nothing
Just x >>= f = f x
fail _ = Nothing
return
関数はpure
と同じで、値をJust
に内包します。bind関数または>>=
は、Nothing
が渡された場合はNothing
を出力し、
それ以外の場合はJust
内の値に対して関数を適用した結果を出力します。これは関数f内でMonadを使うことができるため、非常に便利です。
Applicative
の場合、入力の1つがNothing
でなければNothing
を得ることができませんでした。
これは、関数の出力がMaybe a
内の型,aでなければならず、Maybe a
そのものであってはいけなかったからです。
それに対しMonad
では、型a
の値を取り、Maybe a
を返す関数を持てるようになったので、次のようなことができるようになりました。
safediv :: (Eq a, Fractional a) => a -> a -> Maybe a
safediv x y
| y == 0 = Nothing
| otherwise = Just (x / y)
Just 1 >>= (\x -> safediv x 10) --- (Just 0.1)
Just 1 >>= (\x -> safediv x 0) --- (Nothing)
型(a -> a -> m a)
のsafediv
をMaybe
内の値に適用することはできるようになったことにより、
Nothing
を返す条件をさらに柔軟に設定できるようになり、Nothing
が渡された場合以外にも操作中に失敗を返すことができるようになりました
(y == 0
の時にNothing
を出力できるようになった)。
これにより、エラーを引き起こしにくい関数を簡単に作成できます。
リスト
リストもMonad
の有効なインスタンスであり、次のように定義されています。
instance Monad [] where
return x = [x]
xs >>= f = concat (map f xs)
fail _ = []
return
関数はpure
関数と同じで、値をリストに入れます。bind関数はリスト内の要素に関数をマッピングし、その結果を連結してリストにします。
次の例はリストのbind関数がどのように機能するかを示しています。
[3,4,5] >>= \x -> [x,-x]
--- [3,-3,4,-4,5,-5]
匿名関数\x -> [x, -x]
はリスト内の各要素に適用され、その結果[3, -3]
、[4, -4]
、および[5, -5]
が連結されて最終的なリストが作成されます。
Maybe
とリストの他にも、Monad
型クラスに属する多くの型コンストラクタが存在し、その中にはIO
も含まれます。
Do構文
bind関数を使用して、safediv
のような複数のパラメータを持つ関数を使用する場合、次のように実現できます。
res = Just 1 >>= (\x -> Just 2 >>= (\y -> safediv x y))
しかし、これは書くのも読むのも簡単ではありません。そこで、Haskellはより良い構文を用意してくれています。
res = do
x <- Just 1 --- x = 1
y <- Just 2 --- y = 2
return $ safediv x y --- Just 0.5
おや、この記法はどこかで見たことがありませんか?そうです、これはRoad to Haskeller #12 - Hello, World (IOアクション)!で見た記法です。
<-
は実際には>>=
(どちらもbindと呼ばれています!)と同じもので、do
は括弧を省略するためのものです。
気づいた方もいるかもしれませんが、<-
記法は他の場所でも見たことがあります。それはRoad to Haskeller #3 - リスト & タプルでリスト内包表記を学んだときです!
--- リスト内包表記
res = [2*x | x <- [1,2,3]] --- res = [2,4,6]
--- Do構文
res = do
x <- [1,2,3]
return $ 2 * x --- res = [2,4,6]
--- <<=記法
res = [1,2,3] >>= (\x -> return $ 2 * x) --- res = [2,4,6]
Monad則
型コンストラクタが正しいMonad
であるためには以下の法則を遵守する必要があります。
-
左単位元: 値を
Monad
に入れて、bindを使って関数を適用することは、値を直接関数に渡した場合と同じ結果になる必要があります。 (return x >>= f == f x
) -
右単位元:
Monad
内の値をbindを使ってreturn
に渡した場合、同じ値を持つ同じモナドが結果でなければなりません。 (m >>= return == m
) -
結合性: bindで作られた関数の連鎖がどれほどネストされていても、結果は同じである必要があります。 (
m >>= f >>= g == (m >>= f) >>= g
)
クイズ
この記事では、学習した内容を確認するためのクイズを設けます。記事のメイン部分を読んだ後に、ぜひ自分で問題を解いてみることを強くお勧めします。各問題をクリックすると答えが表示されます。
リソース
- Philipp, Hagenlocher. 2020. Haskell for Imperative Programmers #17 - Monads. YouTube.
- NA. A Fistful of Monads. Learn You a Haskell for Great Good.