4. 第14章 もうちょっとだけモナド
ログを追記していく演算
簡単な例
$ cat hoge.hs
applyLog :: (a, String) -> (a -> (b, String)) -> (b, String)
applyLog (x, log) f = let (y, newLog) = f x
in (y, log ++ newLog)
isBigGang :: Int -> (Bool, String)
isBigGang x = (x > 9, "Compared gang size to 9.")
$ ghci hoge.hs
*Main> (3, "Smallish gang.") `applyLog` isBigGang
(False,"Smallish gang.Compared gang size to 9.")
*Main> (30, "A freaking platoon.") `applyLog` isBigGang
(True,"A freaking platoon.Compared gang size to 9.")
*Main> ("Tobin", "Got outloaw name.") `applyLog` (x -> (length x, "Applied length."))
(5,"Got outloaw name.Applied length.")
4
Open Cloud Campus
5. 第14章 もうちょっとだけモナド
ログ(String)を一般のモノイドに置き換える
ログの本質は追加していけることです。
– そこで、追加操作をモノイドのmappendに置き換えてみます。
applyLog :: (Monoid m) => (a, m) -> (a -> (b, m)) -> (b, m)
applyLog (x, log) f = let (y, newLog) = f x
in (y, log `mappend` newLog)
– モノイドの定義とモノイド則
class Monoid a where
mempty :: a
mappend :: a -> a -> a
結合則 (a mappend b) mappend c == a mappend (b mappend c)
単位元 mempty mappend a == a mappend mempty == a
– モノイドの例
instance Monoid [a] where
mempty = []
mappend = (++)
※Monoidの類は「*」なので具体型が必要
5
Data.Monoidで定義
newtype Sum a = Sum { getSum :: a }
deriving (Eq, Ord, Read, Show, Bounded)
instance Num a => Monoid (Sum a) where
mempty = Sum 0
Sum x `mappend` Sum y = Sum (x + y)
Open Cloud Campus
6. 第14章 もうちょっとだけモナド
ログ(String)を一般のモノイドに置き換える
モノイドの例としてSum Intを取って、合計価格をログとして利用してみます。
$ cat hoge.hs
import Data.Monoid
applyLog :: (Monoid m) => (a, m) -> (a -> (b, m)) -> (b, m)
applyLog (x, log) f = let (y, newLog) = f x
in (y, log `mappend` newLog)
type Food = String
type Price = Sum Int
-- Add appropriate drink for a given food.
addDrink :: Food -> (Food, Price)
addDrink "beans" = ("milk", Sum 25)
addDrink "jerky" = ("whiskey", Sum 99)
addDrink _ = ("beer", Sum 30)
$ ghci hoge.hs
*Main> ("beans", Sum 10) `applyLog` addDrink
("milk",Sum {getSum = 35})
*Main> ("jerky", Sum 25) `applyLog` addDrink
("whiskey",Sum {getSum = 124})
6
Open Cloud Campus
7. 第14章 もうちょっとだけモナド
applyLogをバインド演算に置き換える
「値」と「ログ(モナド)」の組を一般の型aとwに置き換えたものをWriter型と
して定義します。
newtype Writer w a = Writer { runWriter :: (a, w) }
applyLogがバインド演算になるようにWriterモナドを定義します。
※Monadの類は「*->*」なので型パラメータ「a」は含めない
instance (Monoid w) => Monad (Writer w) where
return x = Writer (x, mempty)
(Writer (x, v)) >>= f = let (Writer (y, v')) = f x
in Writer (y, v `mappend` v')
applyLog :: (Monoid m) => (a, m) -> (a -> (b, m)) -> (b, m)
applyLog (x, log) f = let (y, newLog) = f x
in (y, log `mappend` newLog)
7
Open Cloud Campus
8. 第14章 もうちょっとだけモナド
applyLogをバインド演算に置き換える
Monad則の証明
・左恒等性
retrun x >>= f
<=> Writer (x, empty) >>= f
<=> let (Writer (y, v')) = f x in Writer (y, mempty `mappend` v')
<=> let (Writer (y, v')) = f x in Writer (y, v')
<=> f x
・右恒等性
一般にWriter型の値mに対して、m = Writer (m_x, m_v)で、m_xとm_vを定義する。(m_x := fst (runWriter m), m_v := snd (runWriter m))
m >>= return
<=> let (Writer (y, v')) = return m_x in Writer (y, m_v `mappend` v')
<=> let (Writer (y, v')) = Writer (m_x, mempty) in Writer (y, m_v `mappend` v')
<=> Writer (m_x, m_v `mappend` mempty)
<=> Writer (m_x, m_v)
<=> m
・結合則
(m >>= f) <=> Writer ( (f m_x)_x, m_v `mappend` (f m_x)_v )
よって、結合則の左辺は
(m >>= f) >>= g <=> Writer ( (g (f m_x)_x)_x, (m_v `mappend` (f m_x)_v) `mappend` (g (f m_x)_x)_v ) ---(1)
一方、結合則の右辺は
m >>= (x -> f x >>= g) <=> Writer ( ((f m_x) >>= g)_x, m_v `mappend` ((f m_x) >>= g)_v ) ----(2)
ここで
f m_x >>= g <=> Writer ( (g (f m_x)_x)_x, (f m_x)_v `mappend` (g (f m_x)_x)_v)
よって、
(2) <=> Writer ((g (f m_x)_x)_x, m_v `mappend` ((f m_x)_v `mappend` (g (f m_x)_x)_v)) ) ---(3)
(1)と(3)で、mappendの結合則より、(1) <=> (3)
8
Open Cloud Campus
9. 第14章 もうちょっとだけモナド
ログ(String)を一般のモノイドに置き換える
addDrinkの例をWriterモナドで書きなおしてみます。
$ cat hoge.hs
import Data.Monoid
newtype Writer w a = Writer { runWriter :: (a, w) }
instance (Monoid w) => Monad (Writer w) where
return x = Writer (x, mempty)
(Writer (x, v)) >>= f = let (Writer (y, v')) = f x
in Writer (y, v `mappend` v')
type Food = String
type Price = Sum Int
-- Add appropriate drink for a given food.
addDrink :: Food -> Writer Price Food
addDrink "beans" = Writer ("milk", Sum 25)
addDrink "jerky" = Writer ("whiskey", Sum 99)
addDrink _ = Writer ("beer", Sum 30)
$ ghci hoge.hs
*Main> runWriter $ Writer ("beans", Sum 10) >>= addDrink
("milk",Sum {getSum = 35})
*Main> runWriter $ Writer ("jerky", Sum 25) >>= addDrink
("whiskey",Sum {getSum = 124})
9
Open Cloud Campus
10. 第14章 もうちょっとだけモナド
do記法によるWriterモナドの使用例
Writerモナドは、Control.Monad.Writerで定義されています。
– 値コンストラクタが「writer」(頭が小文字)なので注意。
$ cat hoge.hs
import Control.Monad.Writer
logNumber :: Int -> Writer [String] Int
logNumber x = writer (x, ["Got number: " ++ show x])
threeWithLog :: Writer [String] Int
threeWithLog = do
a <- logNumber 3
return a
{threeWithLog = (logNumber 3) >>= (a -> return a)
-}
$ ghci hoge.hs
*Main> runWriter threeWithLog
(3,["Got number: 3"])
*Main> runWriter multWithLog
(15,["Got number: 3","Got number: 5"])
multWithLog :: Writer [String] Int
multWithLog = do
a <- logNumber 3
b <- logNumber 5
return (a*b)
{multWithLog = (logNumber 3) >>= a ->
(logNumber 5) >>= b -> return (a*b)
-}
10
Open Cloud Campus
11. 第14章 もうちょっとだけモナド
do記法によるWriterモナドの使用例
do記法の中に、変数に束縛しないWriter型の値を入れるとログの追記のみが行わ
れます。
$ cat hoge.hs
import Control.Monad.Writer
logNumber :: Int -> Writer [String] Int
logNumber x = writer (x, ["Got number: " ++ show x])
threeWithLog :: Writer [String] Int
threeWithLog = do
a <- logNumber 3
writer ((), ["Hello, World!"])
return a
{threeWithLog = (logNumber 3) >>= a ->
writer ((), ["Hello, World!"]) >>= _ -> return a
-}
※この値は捨てられるので、何でもよい。
$ ghci hoge.hs
*Main> runWriter threeWithLog'
(3,["Got number: 3","Hello, World"])
11
Open Cloud Campus
12. 第14章 もうちょっとだけモナド
do記法によるWriterモナドの使用例
同じことをする関数tellが用意されています。
$ cat hoge.hs
import Control.Monad.Writer
logNumber :: Int -> Writer [String] Int
logNumber x = writer (x, ["Got number: " ++ show x])
multWithLog :: Writer [String] Int
multWithLog = do
a <- logNumber 3
b <- logNumber 5
tell ["Gonna multiply these two"]
return (a*b)
$ ghci hoge.hs
*Main> runWriter multWithLog
(15,["Got number: 3","Got number: 5","Gonna multiply these two"])
tellの定義はこんな感じ(のはず)。
tell :: (Monoid a) => a -> Writer a ()
tell log = writer ((), log)
12
Open Cloud Campus
13. 第14章 もうちょっとだけモナド
再帰処理のログを取得する
ユークリッドの互除法
$ cat hoge.hs
import Control.Monad.Writer
mygcd :: Int -> Int -> Int
mygcd a b
| b == 0
= a
| otherwise = mygcd b (a `mod` b)
gcdWithLog :: Int -> Int -> Writer [String] Int
gcdWithLog a b
| b == 0
= do
tell ["Finished with " ++ show a]
return a
| otherwise = do
tell [show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b)]
gcdWithLog b (a `mod` b)
$ ghci hoge.hs
*Main> mygcd 8 3
1
*Main> runWriter $ gcdWithLog 8 3
(1,["8 mod 3 = 2","3 mod 2 = 1","2 mod 1 = 0","Finished with 1"])
*Main> mapM_ putStrLn $ snd $ runWriter $ gcdWithLog 8 3
8 mod 3 = 2
3 mod 2 = 1
2 mod 1 = 0
Finished with 1
13
Open Cloud Campus
14. 第14章 もうちょっとだけモナド
リストの結合順序を考える
「gcdWithLog 3 2」を運算すると、リストは右結合で展開されるので効率的。
gcdWithLog 3 2
<=> tell ["3 mod 2 = 1"] >>= _ -> gcdWithLog 2 1 ---- (1)
ここで
gcdWithLog 2 1
<=> tell ["2 mod 1
<=> tell ["2 mod 1
<=> tell ["2 mod 1
<=> writer (1, ["2
= 0"]
= 0"]
= 0"]
mod 1
>>= _ -> gcdWithLog 1 0
>>= _ -> (tell ["Finished with 1"] >>= _ -> return 1)
>>= _ -> writer (1, ["Finished with 1"] ++ [])
= 0"] ++ (["finished with 1"] ++ []))
よって
(1) <=> tell ["3 mod 2 = 1"] >>= _ -> writer (1, ["2 mod 1 = 0"] ++ (["finished with 1"] ++ []))
<=> writer (1, ["3 mod 2 = 1"] ++ (["2 mod 1 = 0"] ++ (["finished with 1"] ++ [])))
※リストの右結合と左結合の効率の違い
data [a] = [] | a : [a] deriving (Eq, Ord)
(++) :: [a] -> [a] -> [a]
(++) xs ys = foldr (:) ys xs
右結合 x ++ ys <=> x : xs == O(1)
左結合 xs ++ y <=> foldr (:) xs y == O(n)
14
Open Cloud Campus
15. 第14章 もうちょっとだけモナド
リストの結合順序を考える
一方、計算経過を逆順にログに記録するgcdReverseは、リストが左結合で展開さ
れるので非効率的
$ cat hoge.hs
import Control.Monad.Writer
gcdReverse :: Int -> Int -> Writer [String] Int
gcdReverse a b
| b == 0
= do
tell ["Finished with " ++ show a]
return a
| otherwise = do
result <- gcdReverse b (a `mod` b)
tell [show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b)]
return result
$ ghci hoge.hs
*Main> runWriter $ gcdReverse 3 2
(1,["Finished with 1","2 mod 1 = 0","3 mod 2 = 1"])
gcdReverse 3 2
<=> gcdReverse 2 1 >>= result -> (tell ["3 mod 2 = 1"] >>= (_ -> return result))
<=> gcdReverse 2 1 >>= result -> writer (result, ["3 mod 2 = 1"] ++ []) ---- (1)
ここで
gcdReverse 2 1
<=> gcdReverse 1 0 >>= result -> (tell ["2 mod 1 = 0"] >>= (_ -> return result))
<=> (tell ["Finished with 1"] >>= _ -> return 1) >>= result -> (tell ["2 mod 1 = 0"] >>= (_ -> return result))
<=> writer (1, ["Finished with 1"] ++ []) >>= result -> writer (result, ["2 mod 1 = 0"] ++ [])
<=> writer (1, (["Finished with 1"] ++ []) ++ (["2 mod 1 = 0"] ++ []))
15
よって
(1) <=> writer (1, (["Finished with 1"] ++ []) ++ (["2 mod 1 = 0"] ++ [])) >>= result -> writer (result, ["3 mod 2 = 1"] ++ [])
<=> writer (1, (["Finished with 1"] ++ []) ++ (["2 mod 1 = 0"] ++ []) ++ (["3 mod 2 = 1"] ++ []))
Open Cloud Campus
19. 第14章 もうちょっとだけモナド
(復習)ファンクタとしての関数 「(->) r」
「(->) r」は「rからの関数」を作る型コンストラクタ
(->) r a <=> r -> a
「(->) r」はファンクタ
instance Functor ((->) r) where
fmap f g = f.g
g
f.g
r -> a
(->) r
a
19
fmap f
f
r -> b
(->) r
$ ghci
*Main>
*Main>
*Main>
55
*Main>
55
let f = (*5)
let g = (+3)
fmap f g $ 8
(*5).(+3) $ 8
b
Open Cloud Campus
20. 第14章 もうちょっとだけモナド
(復習)アプリカティブファンクタとしての関数 「(->) r」
「(->) r」はアプリカティブファンクタ
instance Applicative ((->) r) where
pure x = _ -> x
f <*> g = x -> f x (g x)
-- f :: r -> (a -> b)
-- g :: r -> a
-- f <*> g :: r -> b
注)f <$> x = fmap f x
(+)
<=>
<=>
<=>
(+) <$> (*2) <*> (+10) <=> x -> (x*2) + (x+10)
<$> (*2) <*> (+10)
(+).(*2) <*> (+10)
x -> ((+).(*2) x)((+10) x)
x -> (x*2) + (x+10)
ここに共通のパラメータxを注入するイメージ
(*2)
r -> a
(+) <$>
(fmap (+))
f := (+).(*2)
r -> (a -> b)
<*>
g := (+10)
r -> a
共通のパラメータxが与えられた
ものとして演算する
a
20
(+)
a -> b
x -> f x (g x) <=> x -> (x*2)+(x+10)
Open Cloud Campus
21. 第14章 もうちょっとだけモナド
モナドとしての関数 「(->) r」
「(->) r」は次の定義でモナドにもなります。
– アプリカティブファンクタの定義と比較すると f の引数の順序が異なるだけで、本質的に
は同じことをしています。
instance Monad ((->) r) where
return x = _ -> x
g >>= f = x -> f (g x) x
-- f :: a -> (r -> b)
-- g :: r -> a
-- g >>= f :: r -> b
instance Applicative ((->) r) where
pure x = _ -> x
f <*> g = x -> f x (g x)
-- f :: r -> (a -> b)
-- g :: r -> a
-- f <*> g :: r -> b
f := w x -> (x*2) + w
g := x+10
g >>= f <=> x -> (x*2) + (x+10)
f := (+).(*2) <=> f := x w -> (x*2) + w
g := x+10
f <*> g <=> x -> (x*2) + (x+10)
同じ結果
21
Open Cloud Campus
22. 第14章 もうちょっとだけモナド
モナドとしての関数 「(->) r」
モナドにした恩恵として、do記法が使用できます。
– Monadのバインド演算をすべて評価した後に、パラメータxを読んで結果を出すので、
「Readerモナド」と呼ばれます。
$ cat hoge.hs
import Control.Monad.Instances
addStuff :: Int -> Int
addStuff = do
a <- (*2)
b <- (+10)
return (a+b)
instance Monad ((->) r) where
return x = _ -> x
g >>= f = x -> f (g x) x
$ ghci hoge.hs
*Main> addStuff 3
19
addStuff = (*2)
<=> (*2) >>= a
<=> (*2) >>= a
<=> x -> (x*2)
22
>>= a -> ((+10) >>= b -> retrun (a+b))
-> ((+10) >>= b _ -> (a+b))
x -> a + (x+10)
+ (x+10)
Open Cloud Campus
24. 第14章 もうちょっとだけモナド
状態付きの計算とは?
関数内部で「外部の状態」を想定することはできないので、外部の状態に依存した
処理をする際は、「外部の状態」を引数として与えます。
– 例えば、関数randomで3つの乱数(Bool値)を取得するなら、次のようになります。
randomは、乱数の種(ジェネレータ)を取って、乱数と新しい種を返します。
$ cat hoge.hs
import System.Random
-- random :: (RandomGen g, Random a) -> g -> (a, g)
threeCoins :: StdGen -> (Bool ,Bool, Bool)
threeCoins gen =
let (firstCoin, newGen) = random gen
(secondCoin, newGen') = random newGen
(thirdCoin, newGen'') = random newGen'
in (firstCoin, secondCoin, thirdCoin)
$ ghci hoge.hs
*Main> threeCoins (mkStdGen 999)
(True,False,False)
最初の種を作る関数
これを一般化して、次の型の関数を「状態付きの計算」と呼びます。
– sが「状態」で、aが状態に依存した「計算結果」
s -> (a, s)
24
Open Cloud Campus
25. 第14章 もうちょっとだけモナド
状態付きの計算の例
「スタックの状態」を受けて、push/pop操作する関数の例です。
$ cat hoge.hs
type Stack = [Int]
pop :: Stack -> (Int, Stack)
pop (x:xs) = (x, xs)
push :: Int -> Stack -> ((), Stack)
push a xs = ((), a:xs)
$ ghci hoge.hs
*Main> let ((), newStack1) = push 3 []
*Main> let (a, newStack2) = pop newStack1
*Main> let ((), newStack1) = push 3 [5,8,2,1]
*Main> let (a, newStack2) = pop newStack1
*Main> pop newStack2
(5,[8,2,1])
25
Open Cloud Campus
26. 第14章 もうちょっとだけモナド
Stateモナド
状態の受け渡しをバインド演算(>>=)で行えるモナドを構築してみます。
newtype State s a = State { runState :: s -> (a, s) }
instance Monad (State s) where
return x = State $ s -> (x, s)
State g >>= f = State $ s -> let (a, stat) = g s
in runState (f a) stat
--
State g >>= f
= State $ s -> runState (f $ fst (g s)) (snd (g s))
gを実行する直前の状態
s
gが保持する値
26
gを実行した直後の状態
g
a
stat
return xは、「状態は変えず
にxという値を持つ」モナド
f a
f
return x
x
Open Cloud Campus
27. 第14章 もうちょっとだけモナド
StateモナドでStackを実装
Stateモナドは、Control.Monad.Stateで定義されています。
– 値コンストラクタが「state」(頭が小文字)なので注意。
$ cat hoge.hs
import Control.Monad.State
type Stack = [Int]
pop :: State Stack Int
pop = state $ (x:xs) -> (x, xs)
push :: Int -> State Stack ()
push a = state $ xs -> ((), a:xs)
stackManip :: State Stack Int
stackManip = do
push 3
pop
pop
$ ghci hoge.hs
*Main> runState stackManip $ [5,8,2,1]
(5,[8,2,1])
27
Open Cloud Campus
28. 第14章 もうちょっとだけモナド
StateモナドでStackを実装
もう少し複雑な例
$ cat hoge.hs
import Control.Monad.State
$ ghci hoge.hs
type Stack = [Int]
*Main> runState stackStuff [9,0,2,1,0]
((),[8,3,0,2,1,0])
pop :: State Stack Int
pop = state $ (x:xs) -> (x, xs)
*Main> runState stackStuff [5,0,2,1,0]
((),[5,0,2,1,0])
push :: Int -> State Stack ()
push a = state $ xs -> ((), a:xs)
stackManip :: State Stack Int
stackManip = do
push 3
pop
pop
stackStuff :: State Stack ()
stackStuff = do
a <- pop
if a == 5 then push 5
else do push 3
push 8
28
Open Cloud Campus
29. 第14章 もうちょっとだけモナド
StateモナドでStackを実装
getとputで状態の確認と操作ができます。
– getは、現在の状態を値にコピーします。
– putは、現在の状態を指定の状態に強制リセットします。
get = state $ s -> (s, s)
put stat = state $ s -> ((), stat)
push/popをget/putで実装した例
pop :: State Stack Int
pop = do
(x:xs) <- get
put xs
return x
push :: Int -> State Stack Int
push x = do
xs <- get
put (x:xs)
29
Open Cloud Campus
30. 第14章 もうちょっとだけモナド
Stateモナドの型の注意
モナドの類は、「*->*」(型パラメータを1つとって、初めて具体型になる)
– 例えば、リストモナド [] は、[Int], [String], [Char]などさまざまな具体型を包括してい
ます。バインド演算を通して、[Int]を取って、[String]を返すなど、型を変換すること
が可能です。
(>>=) :: [a] -> (a -> [b]) -> [b]
Stateモナドの定義を見ると、「値」の型aが変換可能なパラメータに対応しています。「状
態」の型sは、モナドの定義に固定的に埋め込まれているので、バインド演算で変換すること
はできません。
instance Monad [] where
return x = [x]
xs >>= f = concat (map f xs)
newtype State s a = State { runState :: s -> (a, s) }
instance Monad (State s) where
return x = State $ s -> (x, s)
State g >>= f = State $ s -> let (a, stat) = g s
in runState (f a) stat
30
Open Cloud Campus
31. 第14章 もうちょっとだけモナド
乱数の連続生成をStateモナドで実施
乱数生成関数randomをStateモナドに包むと、乱数の種を「状態」として受け取る
ことができます。
$ cat hoge.hs
import System.Random
import Control.Monad.State
randomSt :: (Random a) => State StdGen a
randomSt = state random
threeCoins :: State StdGen (Bool ,Bool, Bool)
threeCoins = do
a <- randomSt
b <- randomSt
c <- randomSt
return (a, b, c)
$ ghci hoge.hs
*Main> runState threeCoins (mkStdGen 999)
((True,False,False),2063054562 2103410263)
31
Open Cloud Campus
33. 第14章 もうちょっとだけモナド
StateモナドとWriterモナドの比較
Stateモナドの定義を見ながら・・・
newtype State s a = State { runState :: ( s -> (a, s) ) }
instance Monad (State s) where
return x
= State $ s -> (x, s)
State g >>= f = State $ s -> let (a, stat) = g s
in runState (f a) stat
Writerモナドの定義を次のように書き直します。
import Data.Monoid
newtype Writer w a = Writer { runWriter :: (a, w) }
instance (Monoid w) => Monad (Writer w) where
return x
= Writer $ (x, mempty)
Writer g >>= f = Writer $ let (a, log) = g
in (fst $ runWriter (f a),
log `mappend` (snd $ runWriter (f a)))
よく似ています・・・。
33
– Stateモナドは、状態(s)の初期値を受けて、各モナドがその値を変更していきます。一
方、Writerモナドは、ログ(モノイドw)の初期値を受けて、各モナドがログに追記して
いきます。
– つまり、Stateモナドの「状態」にモノイドを指定して、状態の変更操作を「追記」に
限定すると、Writerモナドが得られると想像できます。
Open Cloud Campus
34. 第14章 もうちょっとだけモナド
やってみました
できました。
$ cat hoge.hs
import Data.Monoid
newtype State s a = State { runState :: ( s -> (a, s) ) }
instance Monad (State s) where
return x
= State $ s -> (x, s)
State g >>= f = State $ s -> let (a, stat) = g s
in runState (f a) stat
tell :: (Monoid w) => w -> State w ()
tell log = State $ s -> ((), s `mappend` log)
logNumber :: Int -> State [String] Int
logNumber x = State $ s -> (x, s `mappend` ["Got number: " ++ show x])
multWithLog :: State [String] Int
multWithLog = do
a <- logNumber 3
b <- logNumber 5
tell ["Gonna multiply these two"]
return (a*b)
$ ghci hoge.hs
*Main> runState multWithLog []
(15,["Got number: 3","Got number: 5","Gonna multiply these two"])
34
Open Cloud Campus
35. 第14章 もうちょっとだけモナド
やってみました
Writerモナドの場合は、バインド演算に「ログの追加(mappend)」がハード
コードされているので、tell, logNumberでは、追記するログを与えるだけです。
tell :: (Monoid a) => a -> Writer a ()
tell log = writer ((), log)
logNumber :: Int -> Writer [String] Int
logNumber x = writer (x, ["Got number: " ++ show x])
一方、Stateモナドの場合は、バインド演算時の状態変化は、ユーザが自由に実
装できるので、明示的に`mappend`するように実装しています。
tell :: (Monoid w) => w -> State w ()
tell log = State $ s -> ((), s `mappend` log)
logNumber :: Int -> State [String] Int
logNumber x = State $ s -> (x, s `mappend` ["Got number: " ++ show x])
35
Open Cloud Campus