Haskellのモナド3(4)
mplusとmzeroの他にも、いくつか知っておくべき関数があります。
msum
MonadPlusのインスタンスを扱っている時に、モナドのリスト[Maybe a]、つまり[[a]]を受け取ってそれをmplusで折りたたんでいくという作業をよく行います。msumがまさにそれです。
msum :: MonadPlus m => [m a] -> m a msum = foldr mplus mzero
これは、リストの結合の一般化と考えれば良いです。実際、リストモナドに対するmsumはまさにリストの結合となります。Maybeモナドでは、リスト中の最初のJust xを見つけ、もしJust xが含まれていない場合、Nothingを返します。
guard
guardは、非常に便利な関数で、おそらく知らず知らずのうちに使ったことがあるかと思います。これはリスト内包表記の中でも用いられます。リスト内包表記をリストモナドに戻して書くと、以下のようになりますね。
pythags = [(x, y, z) | z <- [1..], x <- [1..z], y <- [x..z], x^2 + y^2 == z^2]
これは以下のように書きなおすことが出来ました。
pythags = do z <- [1..] x <- [1..z] y <- [x..z] guard (x^2 + y^2 == z^2) return (x, y, z)
guardの定義は以下のようになっています。
guard :: MonadPlus m => Bool -> m () guard True = return () guard False = mzero
具体的には、guardは引数がFalseであるなら呼んだdoブロックにmzeroを返します。MonadPlusの法則として、
mzero >>= hoge == mzero
というのがありましたね。doブロックは>>=演算の塊ですので、mzeroが処理の中に入るとdoブロック全体がmzeroとなります。
もう少し説明するために、上のピタゴラス関数を拡張して、リストモナドの特別なguardを考えてみます。まず、以下がリストモナド用のguardです。
guard :: Bool -> [()] guard True = [()] guard False = []
guardは値を弾きます。例えば、ピタゴラス関数では、「x^2 + y^2 = z^2でない」すべての値を弾いてほしいです。先ほどのdo構文ピタゴラス関数を展開したものをみてみましょう。
pythags = [1..] >>= \z -> [1..z] >>= \x -> [x..z] >>= \y -> guard (x^2 + y^2 == z ^2) >>= \_ -> return (x, y, z)
(>>=)とreturnをリストモナドのそれに置き換えると、以下のようになります。
pythags = let ret x y z = [(x, y, z)] gd z x y = concatMap (\_ -> ret x y z) (guard $ x^2 + y^2 == z^2) doY z x = concatMap (gd z x) [x..z] doX z = concatMap (doY z) [1..z] doZ = concatMap (doX ) [1..] in doZ
もし与えられた引数がFlaseとなったとき、guardがカラリストを結果として返すことに気をつけてください。空リストに対してマッピングした場合、どのような関数を渡しても空リストにしかなりません。そのため、gdの中、guardによって作られた空リストはgdに結び付けられ、結果retは空リストとなります。
なぜこれが大切なのかは、リスト計算というものが木のようなものだと考えればわかります。ピタゴラスの三角形を探索するとき、まずzを決め、そこから派生する答えかもしれない可能性(木)の中、xを決め、そのxから派生する木からyを決める必要があります。ですので、このようなずになるかと思います。
start |__________________________________..... z 1 2 | | | ... | |____.... ____________________..... | | x 1 1 | |_________.... | | y 1 1
x, y, zの組み合わせそれぞれが可能性を表しています。関数がこの木を評価し終わった時、木の下の方から結果をつなげていきます。結果が空リストであれば、この結合作業に全く影響しないので、結果演算が成功します。