Haskell モナドについての学習

個人開発したアプリの宣伝
目的地が設定できる手帳のような使い心地のTODOアプリを公開しています。
Todo with Location

Todo with Location

  • Yoshiko Ichikawa
  • Productivity
  • Free

スポンサードリンク

モナド

モナドはバインド (>>=)関数をサポートするアプリカティブファンクターの一種。

(>>=)関数の定義。

(>>=:: (Monad m) => m a -> (a -> m b) -> m b

Monadという文脈に包んだ値を、値を取る関数に対して渡す。と読める。

適用例。

ghci> Just 3 >>= (\a -> Just(a * 3))
Just 9


つまり、モナドとは値を取る関数に適応できる値を持もてる、型クラスのインスタンスだと思う。

モナド型クラス
class Monad m where
  return :: a -> m a
  (>>=:: m a -> (a -> m b) -> m b

  (>>:: m a -> m b -> m b
  x >> y = x >>= _ -> y

  fail :: String -> m a
  fail msg = error msg

returnはIOアクションを扱った時と同じく、文脈に包んで値を返す。アプリカティブファンクターのpureと同じ役割のメソッド。


Maybe モナド型インスタンス
instance Monad Maybe where
  return x = Just x
  Nothing >>= f = Nothing
  Just x >>= f  = f x
  fail _ = Nothing

関数の実行例

ghci> return "WHAT" :: Maybe String
Just "WHAT"
ghci> Just 9 >>= \x -> return $ x*10
Just 90
ghci> Nothing >>= \x -> return $ x*10
Nothing

最終的に文脈を返すことで、"失敗するかもしれない"や"複数の値が並んでいるかもしれない"、はたまた"副作用があったかもしれない"など、使い手は意識することができる。

do記法

複数モナドの連結は

ghci> Just 3 >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y)))
Just "3!"

このような関数定義におきかえることができ、

foo :: Maybe String
foo = Just 3   >>= (\->
      Just "!" >>= (\->
      Just (show x ++ y)))

そしてdoを使って分かりやすく記述することができる。

foo :: Maybe String
foo = do
    x <- Just 3
    y <- Just "!"
    Just (show x ++ y)”


パターンマッチの失敗
wopwop :: Maybe Char
wopwop = do
    (x:xs) <- Just ""
    return x

このようなパターンマッチの場合、パターンマッチは失敗するのだけど、do構文内での失敗時はfail関数が実行される。try ~ catchのようですね。



リストモナド

リストモナドの定義

instance Monad [] wher
  return x = [x]
  xs >>= f = concat (map f xs)

関数f[]を返す関数になる。つまり、concatを行わないと、[[]]という文脈になり、関数の型と合致しなくなる。よってconcatでフラットにしている。という解釈。

この例がわかりやすい。バインドに渡す関数は[x, -x]と文脈つきで結果を返す必要がある。結果は[[3,-3],[4,-4],[5,-5]]とはならない。

ghci> [3,4,5>>= \-> [x,-x]
[3,-3,4,-4,5,-5]

またリストモナドの連結は

ghci> [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n, ch)
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]

となり、do記法で>>=関数を置き換えると、

listOfTuples :: [(Int, Char)]
listOfTuples = do
    n <- [1,2]
    ch <- ['a','b']
    return (n, ch)
ghci> listOfTuples 
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]

となる。

Scalaのfor内包表記のようですね。


guard関数

モナドにはguard関数という関数が利用できる。

guard :: (MonadPlus m) => Bool -> m ()
guard True = return ()
guard False = mzero

Trueが与えられた時は空のタプルを、Falseの時はmzeroリストの場合は値のない[]を返す。

import Control.Monad
ghci> [1..5] >>= (\x -> guard (True) >> return x)
[1,2,3,4,5]

Trueの場合は [()] >>= _ -> return xreturn xが返る。

ghci> [1..5] >>= (\x -> guard (False) >> return x)
[]

Falseの場合は[] >>= _ -> return x[]が返る。

また、[] >>= \x -> return xとなると、失敗を投げるので、guardはdo構文内でFalseを受け取ると、手続き型言語でいうところのcontinueのような挙動をとる。

import Control.Monad
listOfTuples :: [Int]
listOfTuples = do
    n <- [1,2,3]
    guard (n /= 2)
    return n
ghci> listOfTuples 
[1,3]