※初歩的な部分
Functor
※復習 Functor整理
何かを写すモノという概念を型クラスとして定義したモノ。
fmap :: Functor f => (a -> b) -> f a -> f b
という関数で、f a を f b へと変換(写す)している。
ghci> fmap (\x -> x + 1) (Just 1) -- Just 2
ghci> fmap (\x -> x + 1) [1,2] -- [2,3]
MaybeもリストもFunctorのinstance
Applicative Functor
Functorの拡張版。できることが多いFunctor。
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
--- Controll.Applicative
(<$>) :: Functor f => (a -> b) -> f a -> f b
f <$> x = fmap f x
上記関数をもっていて、関数にもFunctorの文脈を付けられる。※ここのfは型コンストラクタFunctor
ghci> pure (+) <*> Just 3 <*> Just 3 -- Just 6
ghci> (+) <$> Just 3 <*> Just 3 -- Just 6
モナド整理
Applicative Functorの拡張版。要するにFunctorってざっくり理解。
できること
「なんらかの文脈付きa」 と 「純粋なaから文脈付きbを作る関数」をうけとって、「文脈つきb」を作り出す 。
m a -> (a -> m b) -> m b -- m Monad
(a -> m b)という関数が先にあって、(m a)をこの関数に適応させたい時に使える。
type Monad :: (* -> *) -> Constraint
class Applicative m => Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
できること に書いた変換は (>>=) で行える。
instance Monad Maybe where
return = Just
fail = Nothing -- これはシステムからCallされる。
Nothing >>= f = Nothing
(Just x) >>= f = f x
Maybe は Monad。できること の動きが可能となっている。
ghci> Just 9 >>= \x -> return (x + 10)
Just 19
ghci> Nothing >>= \x -> return (x + 10)
Nothing
MonadはFunctorなので、fmap も <*> もMonad関数でできる。
-- f は Functor , m は Monad
fmap :: (a -> b) -> f a -> f b -- functorの関数
(<*>) :: f (a -> b) -> f a -> f b -- applicative functorの関数
(>>=) :: m a -> (a -> m b) -> m b -- monad の関数
-- Functor の fmap は MonadのLiftMで同じことができる
ghci> liftM (+2) (Just 8)
Just 10
ghci> fmap (+2) (Just 8)
Just 10
-- Applicative の <*> は Monadのapで同じことができる
ghci> Just (+2) `ap` Just 8
Just 10
ghci> Just (+2) <*> Just 8
Just 10
何に役立つのか
以下のようなパターンマッチが、モナドの文脈(a -> m b)に処理を任せられるので、不要になる。
case ... of
Nothing -> Nothing
Just x -> case ... of
Nothing -> Nothing
Just y -> ...
たとえば + と - の演算ができる逆ポーランド記法計算機を普通に実装するとこんな感じ
-- input : "12+2-" ans 1
resolveRPN :: String -> Double
resolveRPN st = head $ foldl foldingFunc [] (words st)
where foldingFunc (x:y:ys) "+" = (x+y):ys
foldingFunc (x:y:ys) "-" = (x-y):ys
foldingFunc xs number = read number:xs
これに失敗するかも という文脈を与えたい
monadRPN :: String -> Maybe Double
畳み込みに使用する補助関数の出口をMaybeに変更
-- String -> Intへの変換をMaybeで扱う
readMaybe :: (Read a) => String -> Maybe a
readMaybe st = case reads st of [(x, "")] -> Just x
_ -> Nothing
-- 畳み込み関数にもMaybeを扱う
foldingFunc :: [Double] -> String -> Maybe [Double]
foldingFunc (x:y:ys) "+" = Just ((x+y):ys)
foldingFunc (x:y:ys) "-" = Just ((x-y):ys)
foldingFunc (x:y:ys) "*" = Just ((x-y):ys)
foldingFunc xs number = liftM (:xs) (readMaybe number)
失敗の文脈を与えたRPN
-- Maybe [Double] のリスト内が一つの結果になっているかを確認する
checkSize :: [Double] -> Maybe Double
checkSize [result] = return result
checkSize _ = Nothing
monadRPN :: String -> Maybe Double
monadRPN st = foldM foldingFunc [] (words st) >>= checkSize
monadRPN "1 2 * 4 +" -- Just 6.0
monadRPN "1 2 * 4 + 5 *" -- Just 30.0
monadRPN "1 2 * 4" -- Nothing
monadRPN "1 8 jifa" -- Nothing
checkSizeはDo記法を使うと省略可能
monadRPN st = do
[result] <- foldM foldingFunc [] (words st)
return result
いまのMaybe値がNothingなのかJustなのか、と言った関心を持つ必要がないのが利点
補足
他、
リストもモナドだが、モノイドあたりから解説書きたいので別記事でやるかも(Maybe)