Haskellのモナドいろいろ (4)

Readerモナド

Readerモナドは、読み取り専用のStateモナドのようなものである。
Stateモナドグローバル変数のようなものとして利用できたが、
Readerモナドはグローバル定数のようなものとして利用できる。
Stateモナドでは状態の取得にgetを用いたが、Readerモナドでは ask を用いる。
Stateモナドの runState に相当するのは runReader だが、状態は変化しないので値のみ返す。

import Control.Monad.Reader

calcTax :: Int -> Reader Int Int
calcTax x = do
    tax <- ask
    let y = (x * (100 + tax)) `div` 100
    return y

main :: IO ()
main = do
    print $ (`runReader` 8) $ do
        a <- calcTax 100
        b <- calcTax 500
        c <- calcTax 1000
        return (a, b, c) -- (108,540,1080)
    print $ (`runReader` 10) $ do
        a <- calcTax 100
        b <- calcTax 500
        c <- calcTax 1000
        return (a, b, c) -- (110,550,1100)

モナドだから return で値を中に入れることができる。

import Control.Monad.Reader

hoge :: Int -> Reader String Int -- 状態: String, 値: Int
hoge x = return x

main :: IO ()
main = do
    let ma = hoge 5
    let a = runReader ma "hoge"
    print a -- 5    ※ Stateとは異なり、状態は返さない

モナドだから join で一皮むくことができるが、 join で裸にはできない。

import Control.Monad
import Control.Monad.Reader

hoge :: Int -> Reader s Int
hoge x = return x

main :: IO ()
main = do
    let ma  = hoge 5 :: Reader String Int
    let mma = return ma :: Reader String (Reader String Int)
    let ma' = join mma -- Reader String Int
--  let a'  = join ma'  -- エラー
    let a'  = runReader ma' "hoge"
    print a' -- 5

モナドだから fmap で中の値を関数に渡すことができる。

import Control.Monad.Reader

hoge :: Int -> Reader String Int
hoge x = return x

main :: IO ()
main = do
    let ma = hoge 5 :: Reader String Int
    let mb = fmap (*2) ma
    let b = runReader mb "hoge"
    print b -- 10

モナドだから bind ( >>= ) で中の値を、モナドを返す関数に渡すことができる。

import Control.Monad.Reader

hoge :: Int -> Reader String Int
hoge x = return (x * 2)

main :: IO ()
main = do
    let ma = hoge 5
    let mb = ma >>= hoge
    let a = runReader ma "hoge"
    let b = runReader mb "piyo"
    print a -- 10
    print b -- 20

またすでに見た通り、モナドだから do構文が使える。

Writerモナド

Writerモナドは、追記専用のStateモナドのようなものである。
値の更新ではなく追記であることに注意。主にリストに追加して使う。
ログを取るために使われることが多い。
Stateモナドでは状態の更新に put / modify を用いたが、Writerモナドでは tell を用いる。
Stateモナドの runState, execState に相当するのは runWriter, execWriter である。

import Control.Monad.Writer

main :: IO ()
main = do
    let s = execWriter $ do
            tell "hoge\n"
            tell "piyo\n"
            tell "huga\n"
            return ()
    putStrLn s

モナドだから return で値を中に入れることができる。

import Control.Monad.Writer

hoge :: Int -> Writer String Int -- 状態: String, 値: Int
hoge x = return x

main :: IO ()
main = do
    let ma = hoge 5
    let a = runWriter ma -- 初期値が無いことに注意
    print a -- (5,"")

モナドだから join で一皮むくことができるが、 join で裸にはできない。

import Control.Monad
import Control.Monad.Writer

hoge :: Int -> Writer String Int
hoge x = return x

main :: IO ()
main = do
    let ma  = hoge 5 :: Writer String Int
    let mma = return ma :: Writer String (Writer String Int)
    let ma' = join mma -- Writer String Int
--  let a'  = join ma'  -- エラー
    let a'  = runWriter ma'
    print a' -- (5,"")

モナドだから fmap で中の値を関数に渡すことができる。

import Control.Monad.Writer

hoge :: Int -> Writer String Int
hoge x = return x

main :: IO ()
main = do
    let ma = hoge 5 :: Writer String Int
    let mb = fmap (*2) ma
    let b = runWriter mb
    print b -- (10,"")

モナドだから bind ( >>= ) で中の値を、モナドを返す関数に渡すことができる。

import Control.Monad.Writer

hoge :: Int -> Writer String Int
hoge x = return (x * 2)

main :: IO ()
main = do
    let ma = hoge 5
    let mb = ma >>= hoge
    let a = runWriter ma
    let b = runWriter mb
    print a -- (10,"")
    print b -- (20,"")

またすでに見た通り、モナドだから do構文が使える。