オープンクラウド・キャンパス

第14章
もうちょっとだけモナド

中井悦司
Twitter @enakai00
14.1 Writer? 中の人なんていません!
第14章 もうちょっとだけモナド

ログを追記していく演算
 やりたいことのイメージ
現在値
・・・
・・・

3
×2

・・・
・・・
2倍した

6

ログファイル

 ちょっと一般化
現在値
・・・
・・・

x

f x = (y, newLog)

・・・
・・・
newLog

y

applyLog :: (a, String) -> (a -> (b, String)) -> (b, String)
applyLog (x, log) f = let (y, newLog) = f x
in (y, log ++ newLog)
3

Open Cloud Campus
第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
第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
第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
第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
第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
第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
第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
第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
第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
第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章 もうちょっとだけモナド

リストの結合順序を考える
 「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
第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
第14章 もうちょっとだけモナド

リストの結合を効率的に扱うテクニック
 次のDiffList(差分リスト)を使うと、リストの結合処理を効率化できます。
– 結合の評価を任意のタイミングまで遅延させて、最後に右結合で評価させます。
newtype DiffList a = DiffList { getDiffList :: [a] -> [a] }
toDiffList :: [a] -> DiffList a
toDiffList ys = DiffList (xs -> ys ++ xs)
fromDiffList :: DiffList a -> [a]
fromDiffList (DiffList f) = f []
instance Monoid (DiffList a) where
mempty = DiffList (xs -> [] ++ xs)
(DiffList f) `mappend` (DiffList g) = DiffList (xs -> f.g $ xs)
result = (toDiffList "dog ") `mappend` (toDiffList "meat ") `mappend` (toDiffList "is good!")
<=> result = (xs -> "dog " ++ xs).(xs -> "meat " ++ xs).(xs -> "is good!" ++ xs)
fromDiffList result
<=> "dog " ++ ("meat " ++ ("is good!" ++ []))

16

※fromDiffListを適用したタイミングで右結合で評価される

Open Cloud Campus
第14章 もうちょっとだけモナド

リストの結合を効率的に扱うテクニック
 [String]の代わりにDiffList Stringを使うように書き換えたgcdReverse
$ cat hoge.hs
import Data.Monoid
import Control.Monad.Writer
newtype DiffList a = DiffList { getDiffList :: [a] -> [a] }
toDiffList :: [a] -> DiffList a
toDiffList ys = DiffList (xs -> ys ++ xs)
fromDiffList :: DiffList a -> [a]
fromDiffList (DiffList f) = f []
instance Monoid (DiffList a) where
mempty = DiffList (xs -> [] ++ xs)
(DiffList f) `mappend` (DiffList g) = DiffList (xs -> f.g $ xs)
gcdReverse :: Int -> Int -> Writer (DiffList String) Int
gcdReverse a b
| b == 0
= do
tell (toDiffList ["Finished with " ++ show a])
return a
| otherwise = do
result <- gcdReverse b (a `mod` b)
tell (toDiffList [show a ++ " mod " ++ show b ++
" = " ++ show (a `mod` b)])
return result

※fromDiffListを適用したタイミングで右結合で評価される

$ ghci hoge.hs
*Main> fromDiffList . snd . runWriter $ gcdReverse 110 34
["Finished with 2","8 mod 2 = 0","34 mod 8 = 2","110 mod 34 = 8"]

17

Open Cloud Campus
14.2 Reader? それはあなたです!
第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
第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
第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
第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
14.3 計算の状態の正体
第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
第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
第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
第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
第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
第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
第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
第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
おまけ
StateモナドでWriterモナドを実装
第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
第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
第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
第14章 もうちょっとだけモナド

Q&A

36

Open Cloud Campus
オープンクラウド・キャンパス

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

中井悦司
Twitter @enakai00

Haskell勉強会 14.1〜14.3 の説明資料