fugafuga.write

日々のログ

すごいH本 part96

便利なモナディック関数

モナドを扱う関数はモナディック関数と呼ばれる。

liftM

関数とモナド値をとって、関数でモナド値を写してくれる。 fmapっぽい。

liftM :: Monad m => (a1 -> r) -> m a1 -> m r

fmap の型

fmap :: Functor f => (a -> b) -> f a -> f b

ファンクター則とモナド則を満たしている場合、fmap と liftM はまったく同じものになる。

liftM を試す

*Main> liftM (*3) (Just 8)
Just 24
*Main> fmap (*3) (Just 8)
Just 24
*Main> runWriter $ liftM not $ writer (True, "chickpeas")
(False,"chickpeas")
*Main> runWriter $ fmap not $ writer (True, "chickpeas")
(False,"chickpeas")
*Main> runState (liftM (+100) pop) [1,2,3,4]
(101,[2,3,4])
*Main> runState (fmap (+100) pop) [1,2,3,4]
(101,[2,3,4])

fmap と liftM は同じ動きしてる。

次はアプリカティブ値

*Main> (+) <$> Just 3 <*> Just 5
Just 8
*Main> (+) <$> Just 3 <*> Nothing
Nothing

<$>はただのfmap。<*>はこう

(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b

fmap に似ているが、関数自身も文脈の中に入っている。

ap という関数があり、本質的には<*>と同じだが、Applicativeの代わりにMonad型クラス制約がついている。

ap :: (Monad m) => m (a -> b) -> m a -> m b
ap mf m = do
    f <- mf
    x <- m
    return (f x)

mf は結果が関数であるようなモナド値。

使ってみる

*Main> Just (+3) <*> Just 4
Just 7
*Main> Just (+3) `ap` Just 4
Just 7
*Main> [(+1),(+2),(+3)] <*> [10,11]
[11,12,12,13,13,14]
*Main> [(+1),(+2),(+3)] `ap` [10,11]
[11,12,12,13,13,14]

モナドの威力は少なくともアプリカティブやファンクター以上である。 すべてのモナドはファンクターでもアプリカティブでもあるのにそのインスタンスになっているとは限らない。 また、ファンクターやアプリカティブファンクターが使う関数と等価なモナド版の関数が存在する。

join

任意の入れ子になったモナドは平らにできる。 このために join がある。

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

使ってみる

*Main> join (Just (Just 9))
Just 9
*Main> join (Just Nothing)
Nothing
*Main> join Nothing
Nothing
*Main> join [[1,2,3],[4,5,6]]
[1,2,3,4,5,6]
*Main> runWriter $ join (writer (writer (1, "aaa"), "bbb"))
(1,"bbbaaa")
*Main> join (Right (Right 9))
Right 9
*Main> join (Right (Left "error"))
Left "error"
*Main> join (Left "error")
Left "error"
*Main> runState (join (state $ \s -> (push 10, 1:2:s))) [0,0,0]
((),[10,1,2,0,0,0])

filterM

filter関数は、Haskellプログラミングの米らしい。 map は塩らしい。

filterは述語とフィルタ対象のリストを取り、述語を満たす要素だけを残してくれる。

filter :: (a -> Bool) -> [a] -> [a]

文脈を持った値を filterしたい場合、filterMを使う。Control.Monad モジュールに定義されている。

filterM :: (Monad m) => (a -> m Bool) -> [a] -> m [a]

リストを取って、4より小さい要素だけを残す関数。

*Main> filter (\x -> x < 4) [9,1,5,2,10,3]
[1,2,3]

True か False だけを返すのではなく、何をしたかのログを残すような述語を作る。

import           Control.Monad.Writer.Lazy

keepSmall :: Int -> Writer [String] Bool
keepSmall x
    | x < 4 = do
        tell ["Keeping " ++ show x]
        return True
    | otherwise = do
        tell [show x ++ " is too large, throwning it away"]
        return False

実行

*Main> fst $ runWriter $ filterM keepSmall [9,1,5,2,10,3]
[1,2,3]

ログを表示してみる

*Main> mapM_ putStrLn $ snd $ runWriter $ filterM keepSmall [9,1,5,2,10,3]
9 is too large, throwning it away
Keeping 1
5 is too large, throwning it away
Keeping 2
10 is too large, throwning it away
Keeping 3

filterM を使って冪集合を作る

powerset :: [a] -> [a]
powerset xs = filterM (\x -> [True, False]) xs

実行

*Main> powerset [1,2,3]
[[1,2,3],[1,2],[1,3],[1],[2,3],[2],[3],[]]

foldM

foldl のモナド版が foldM

foldl の型

foldl :: (a -> b -> a) -> a -> [b] -> a

foldM の型

foldM :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a

foldl 使ってみる

*Main> foldl (\acc x -> acc + x) 0 [2,8,3,1]
14

整数のリストを加算したいが、リストのいずれかの要素が9より大きい場合、 計算全体を失敗させたい。Maybeアキュムレータを返すようにする。

import           Control.Monad

binSmalls :: Int -> Int -> Maybe Int
binSmalls acc x
    | x > 9 = Nothing
    | otherwise = Just (acc + x)

実行

*Main> foldM binSmalls 0 [2,8,3,1]
Just 14
*Main> foldM binSmalls 0 [2,11,3,1]
Nothing

リストに9より大きい数が入っている場合、Nothingになっている。

所感

モナディック関数。中2っぽくてよい。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

  • 作者: MiranLipovaca
  • 出版社/メーカー: オーム社
  • 発売日: 2017/07/14
  • メディア: Kindle版
  • 購入: 4人 クリック: 9回
  • この商品を含むブログを見る