[Haskell]Maybeモナドまとめ

エラーが起こる関数

次の関数 fb は負数を与えるとエラーになる。

-- フィボナッチ数列
fib:: Int -> Int
fib 0 = 0
fib 1 = 1
fib n | n > 1 = fib (n - 2) + fib (n - 1)

main = do
    print $ fib 20   -- 6765
    print $ fib (-1) -- エラー

そこでMaybeモナド

Maybeモナドを使うとエラーを戻り値 Nothing として扱える。

-- フィボナッチ数列
fib:: Int -> Maybe Int
fib 0 = Just 0
fib 1 = Just 1
fib n | n > 1 = do
    a <- fib (n - 2)
    b <- fib (n - 1)
    Just (a + b)
fib n | n < 0 = Nothing

main = do
    print $ fib 20   -- Just 6765
    print $ fib (-1) -- Nothing

Just は return と書いても同じ。

-- フィボナッチ数列
fib:: Int -> Maybe Int
fib 0 = return 0
fib 1 = return 1
fib n | n > 1 = do
    a <- fib (n - 2)
    b <- fib (n - 1)
    return (a + b)
fib n | n < 0 = Nothing

ファンクタ<$>やアプリカティブファンクタ<*>を用いて次のようにも書ける

-- フィボナッチ数列
fib:: Int -> Maybe Int
fib 0 = return 0
fib 1 = return 1
fib n | n > 1 = (+) <$> fib (n - 2) <*> fib (n - 1)
fib n | n < 0 = Nothing

fromMaybe関数

fromMaybe関数を使うと、Maybeモナドから値を取り出せる。
第一引数にはデフォルト値(Nothingだったときの値)を渡す。

import Data.Maybe

-- フィボナッチ数列
fib:: Int -> Maybe Int
fib 0 = return 0
fib 1 = return 1
fib n | n > 1 = (+) <$> fib (n - 2) <*> fib (n - 1)
fib n | n < 0 = Nothing

main = do
    print $ fromMaybe 0 (fib 20)   -- 6765
    print $ fromMaybe 0 (fib (-1)) -- 0

bind (=<<)

Maybeモナドを返す関数にMaybeモナドを渡すには bind (=<<) を用いる。

-- フィボナッチ数列
fib:: Int -> Maybe Int
fib 0 = return 0
fib 1 = return 1
fib n | n > 1 = (+) <$> fib (n - 2) <*> fib (n - 1)
fib n | n < 0 = Nothing

-- 1から9の英語
english:: Int -> Maybe String
english n | (n >= 1) && (n <= 9) = return s
  where
    words = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
    s = words !! (n-1)
english _ = Nothing

main = do
    print $ english =<< fib 6    -- Just "eight"
    print $ english =<< fib 20   -- Nothing
    print $ english =<< fib (-1) -- Nothing

ちなみに、この関数 english は次のようにも書ける。

english:: Int -> Maybe String
english n = do
    let words = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
    if (n >= 1) && (n <= 9) then return (words !! (n-1)) else Nothing
english:: Int -> Maybe String
english n = do
    let words = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
    when (n < 1) Nothing
    when (n > 9) Nothing
    return (words !! (n-1))

ファンクタ(<$>) と アプリカティブ(<*>) と バインド (=<<) の違い

<$>:モナドから値を取り出して、ふつうの関数に渡し、戻り値を(同種の)モナドに入れる。
<*>:モナドから値を取り出して、(同種の)モナドに入った関数に渡す。戻り値は(同種の)モナドに入る。
=<<:モナドから値を取り出して、(同種の)モナドを返す関数に渡す。

huga:: Int -> Int
huga n = n * n

hoge:: Maybe (Int -> Int)
hoge = Just (\x -> x * x)

piyo:: Int -> Maybe Int
piyo n = Just (n * n)

main = do
    print $ huga <$> Just 3  -- Just 9
    print $ hoge <*> Just 3  -- Just 9
    print $ piyo =<< Just 3  -- Just 9

また、ファンクタは fmap関数で書いても同じ。

    print $ huga <$> Just 3     -- Just 9
    print $ fmap huga (Just 3)  -- Just 9