モナド・・
そもそもすごいHaskellたのしく学ぼう!を読む動機となったキーワード。関数プログラミング実践入門を読むだけではまだまだ概念を捉えきれず、理解できなかった。
学ぶうちにモナドは型クラスであり、関連する型クラスとして Functor
、Applicative
があることを知った。全体として、それぞれの型クラスができることとの差異を整理することでモナドの必要性やその位置付けの整理に役立てたいと考えている。
##モナドを使う動機
Haskell では、ある型 a
をとって、 a
を返す関数 は、その関数を続けざまに使用することができる。
func :: Int -> Int -> Int
func 3 (func 4 (func 5 ・・・ といったように続けざまに使用できる
また、もし結果が失敗するかもしれないような計算をする関数の場合は返り値の型を Maybe a
のようにしておきたい。
例えば、あるリストに要素 x が含まれているかどうかを調べ、含まれていればその要素を、含まれていなければ
Nothing
を返すような関数のこと。単純にBool
値 を返す関数よりも利用できる範囲が広がるだろう。
この考えは自然なので、最初のfunc
にこの文脈を取り入れることにする。すると、型 a
をとって型 Maybe a
を返す関数となり、続けざまに使用することはできなくなってしまう。
func :: Int -> Int -> Maybe Int
func 3 (func 4 (func 5 ・・・ といったように続けざまに使用できない!
有効な値がある以上は続けざまに使えると便利な関数なので、引数と返り値の型はちょっと違うけれどなんとか実現できないか?
これに答えてくれるのがモナドということになる。
(Just 3) >>= func 4 >>= func 5 >>= ・・・ bind演算子を使って実現できた!
モナドの一般的な概要
モナドは、計算する上で生まれる特定の文脈を効率的に扱うための仕掛け を実現するためにある。一般的な概要については次のリンクにまとめています。
bind 演算子
先述した要求に応えてくれる演算子として、>>=
( bind 演算子 )がある。
引数と返り値の型はちょっと違うけれどなんとか実現できないか?という要求
Maybe
モナドの実装を例に見てみると、
instance Monad Maybe where
(Just x) >>= k = k x
Nothing >>= _ = Nothing
(Just _) >> k = k
Nothing >> _ = Nothing
return = Just
fail _ = Nothing
有効な値を引数にとって、第2引数( 右辺 )の関数を適用している。また、第1引数( 左辺 )が無効値だったら、結果も無効な値としている。
bind 演算子の型は(>>=) :: m a -> (a -> m b) -> m b
なので、次のように書ける。
Just 3 >>= \x -> return (x + 5) >>= \x -> return (x * x) -- Just 64
ちなみに、Functor
型クラスや Applicative
型クラスのインスタンスではこのように内部の値同士が相互作用するような操作は難しい。
Functor/Applicative/Monad
文脈を持った計算を行うために、Functor
、 Applicative
、Monad
という型クラスがある。より柔軟にこの計算を行える(≒強化版)という概念を**<** で表すと、**Functor
< Applicative
< Monad
**ということになる。
それぞれの型クラスについての概要は次のページにまとめています。
これらの型クラスを、特徴、できること、できないことの3つで比較したマトリクスが下表となる。
型クラス | 特徴 | できること | できないこと |
---|---|---|---|
Functor | 文脈付きの値に普通の関数を適用する |
fmap で普通の関数を文脈付きの関数に lifting する1
|
fmap された関数を文脈付きの値に簡単に適用する方法がない |
Applicative | 文脈付きの値に普通の関数(または普通の関数を文脈付きの関数に持ち上げた関数)を適用する | アプリカティブ・スタイルを使って文脈を維持しながら普通の関数を適用する2 | アプリカティブ・スタイルでは、ある文脈付きの値が他の文脈付きの値に依存するような操作の記述が難しい |
Monad |
文脈付きの値に、普通の値をとって、文脈付きの値を返す関数を適用する bind演算子をサポートしたアプリカティブ・ファンクター |
bind演算子で文脈付きの値同士が相互作用するような操作を行う | --- |
普通の関数: 普通の値をとって、普通の値を返す関数(普通でない ≒ リストや
Maybe
などの文脈付きの値 )
モナド型クラスのインスタンス
主要なインスタンスについては、
- モナドについて勉強したことの整理(個別モナド) に整理しています。
MonadPlusとguard関数
まず、MonadPlus
型クラス定義には次にようにある。
-- The MonadPlus class definition
-- | Monads that also support choice and failure.
class Monad m => MonadPlus m where
-- | the identity of 'mplus'. It should also satisfy the equations
--
-- > mzero >>= f = mzero
-- > v >> mzero = mzero
--
mzero :: m a
-- | an associative operation
mplus :: m a -> m a -> m a
モナドは、選別と失敗もサポートしていて、
MonadPlus
のインスタンスになればその恩恵を受けられるということだろう。
MonadPlus
は、モノイドの性質を併せ持つモナドを表す型クラス。リスト[]
も MonadPlus
のインスタンスになれる。
instance MonadPlus [] where
mzero = []
mplus = (++)
instance MonadPlus Maybe where
mzero = Nothing
Nothing `mplus` ys = ys
xs `mplus` _ys = xs
また、guard
関数がある。これは Bool
値をとって、空タプルを最小限の文脈に包むか、mzero
を返す。
-- | @'guard' b@ is @'return' ()@ if @b@ is 'True',
-- and 'mzero' if @b@ is 'False'.
guard :: (MonadPlus m) => Bool -> m ()
guard True = return ()
guard False = mzero
False
が渡ってくると mzero
を返している。つまり、mzero
は 失敗を意味するモナド値 となる。また、引数には通常、条件式が渡され、その真偽値によって結果が 選別されることになる。
guard
関数は、モナド値を扱った計算なのでdo記法で記述することができる。
sevensOnly :: [Int]
sevensOnly = do
x <- [0..50]
guard ('7' `elem` show x) -- 要素に文字7が含まれているかどうか
return x --[7,17,27,37,47]
このとき、最後の return x
を忘れてしまうと guard
関数の返り値 [()]
が結果となってしまうので注意する。