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

37
中井悦司 Twitter @enakai00 オープンクラウド・キャンパス 第14章 もうちょっとだけモナド

Upload: etsuji-nakai

Post on 12-May-2015

987 views

Category:

Technology


1 download

TRANSCRIPT

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

中井悦司Twitter @enakai00

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

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

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

14.1 Writer? 中の人なんていません!

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

Open Cloud Campus3

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

ログを追記していく演算

やりたいことのイメージ

ちょっと一般化

・・・・・・

ログファイル

3

現在値

・・・・・・2倍した

6

×2

・・・・・・

x

現在値

・・・・・・newLog

yf x = (y, newLog)

applyLog :: (a, String) -> (a -> (b, String)) -> (b, String)

applyLog (x, log) f = let (y, newLog) = f x in (y, log ++ newLog)

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

Open Cloud Campus4

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

ログを追記していく演算

簡単な例

$ cat hoge.hsapplyLog :: (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.")

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

Open Cloud Campus5

第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 = (++)

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)

Data.Monoidで定義

※Monoidの類は「*」なので具体型が必要

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

Open Cloud Campus6

第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 = Stringtype 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})

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

Open Cloud Campus7

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

applyLogをバインド演算に置き換える

「値」と「ログ(モナド)」の組を一般の型aとwに置き換えたものをWriter型として定義します。

applyLogがバインド演算になるようにWriterモナドを定義します。

newtype Writer w a = Writer { runWriter :: (a, w) }

applyLog :: (Monoid m) => (a, m) -> (a -> (b, m)) -> (b, m)applyLog (x, log) f = let (y, newLog) = f x in (y, log `mappend` newLog)

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')

※Monadの類は「*->*」なので型パラメータ「a」は含めない

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

Open Cloud Campus8

第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)

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

Open Cloud Campus9

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

ログ(String)を一般のモノイドに置き換える

addDrinkの例をWriterモナドで書きなおしてみます。

$ cat hoge.hs import Data.Monoidnewtype 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 = Stringtype Price = Sum Int-- Add appropriate drink for a given food.addDrink :: Food -> Writer Price FoodaddDrink "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})

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

Open Cloud Campus10

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

do記法によるWriterモナドの使用例

Writerモナドは、Control.Monad.Writerで定義されています。– 値コンストラクタが「writer」(頭が小文字)なので注意。

$ cat hoge.hs import Control.Monad.Writer

logNumber :: Int -> Writer [String] IntlogNumber x = writer (x, ["Got number: " ++ show x])

threeWithLog :: Writer [String] IntthreeWithLog = do a <- logNumber 3 return a{-threeWithLog = (logNumber 3) >>= (\a -> return a)-}

multWithLog :: Writer [String] IntmultWithLog = do a <- logNumber 3 b <- logNumber 5 return (a*b){-multWithLog = (logNumber 3) >>= \a -> (logNumber 5) >>= \b -> return (a*b)-}

$ ghci hoge.hs*Main> runWriter threeWithLog(3,["Got number: 3"])

*Main> runWriter multWithLog(15,["Got number: 3","Got number: 5"])

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

Open Cloud Campus11

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

do記法によるWriterモナドの使用例

do記法の中に、変数に束縛しないWriter型の値を入れるとログの追記のみが行われます。$ cat hoge.hs import Control.Monad.Writer

logNumber :: Int -> Writer [String] IntlogNumber x = writer (x, ["Got number: " ++ show x])

threeWithLog :: Writer [String] IntthreeWithLog = 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"])

※この値は捨てられるので、何でもよい。

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

Open Cloud Campus12

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

do記法によるWriterモナドの使用例

同じことをする関数tellが用意されています。

tellの定義はこんな感じ(のはず)。

$ cat hoge.hs import Control.Monad.Writer

logNumber :: Int -> Writer [String] IntlogNumber x = writer (x, ["Got number: " ++ show x])

multWithLog :: Writer [String] IntmultWithLog = 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 :: (Monoid a) => a -> Writer a ()tell log = writer ((), log)

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

Open Cloud Campus13

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

再帰処理のログを取得する

ユークリッドの互除法$ cat hoge.hsimport Control.Monad.Writermygcd :: Int -> Int -> Intmygcd a b | b == 0 = a | otherwise = mygcd b (a `mod` b)

gcdWithLog :: Int -> Int -> Writer [String] IntgcdWithLog 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 31*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 38 mod 3 = 23 mod 2 = 12 mod 1 = 0Finished with 1

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

Open Cloud Campus14

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

リストの結合順序を考える

「gcdWithLog 3 2」を運算すると、リストは右結合で展開されるので効率的。gcdWithLog 3 2<=> tell ["3 mod 2 = 1"] >>= \_ -> gcdWithLog 2 1 ---- (1)

ここでgcdWithLog 2 1<=> tell ["2 mod 1 = 0"] >>= \_ -> gcdWithLog 1 0<=> tell ["2 mod 1 = 0"] >>= \_ -> (tell ["Finished with 1"] >>= \_ -> return 1)<=> tell ["2 mod 1 = 0"] >>= \_ -> writer (1, ["Finished with 1"] ++ []) <=> writer (1, ["2 mod 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)

※リストの右結合と左結合の効率の違い

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

Open Cloud Campus15

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

リストの結合順序を考える

一方、計算経過を逆順にログに記録するgcdReverseは、リストが左結合で展開されるので非効率的$ cat hoge.hsimport Control.Monad.WritergcdReverse :: Int -> Int -> Writer [String] IntgcdReverse 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"] ++ []))

よって(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"] ++ []))

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

Open Cloud Campus16

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

リストの結合を効率的に扱うテクニック

次のDiffList(差分リスト)を使うと、リストの結合処理を効率化できます。– 結合の評価を任意のタイミングまで遅延させて、最後に右結合で評価させます。

newtype DiffList a = DiffList { getDiffList :: [a] -> [a] }

toDiffList :: [a] -> DiffList atoDiffList 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!" ++ [])) ※fromDiffListを適用したタイミングで右結合で評価される

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

Open Cloud Campus17

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

リストの結合を効率的に扱うテクニック

[String]の代わりにDiffList Stringを使うように書き換えたgcdReverse$ cat hoge.hsimport Data.Monoidimport Control.Monad.Writer

newtype DiffList a = DiffList { getDiffList :: [a] -> [a] }

toDiffList :: [a] -> DiffList atoDiffList 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) IntgcdReverse 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

$ 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"]

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

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

14.2 Reader? それはあなたです!

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

Open Cloud Campus19

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

(復習)ファンクタとしての関数 「(->) r」

「(->) r」は「rからの関数」を作る型コンストラクタ

「(->) r」はファンクタ

(->) r a <=> r -> a

a b

r -> a r -> b

(->) r (->) rf

fmap fg f.g

instance Functor ((->) r) where fmap f g = f.g

$ ghci*Main> let f = (*5)*Main> let g = (+3)*Main> fmap f g $ 855*Main> (*5).(+3) $ 855

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

Open Cloud Campus20

第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

(+) <$> (*2) <*> (+10)<=> (+).(*2) <*> (+10)<=> \x -> ((+).(*2) x)((+10) x)<=> \x -> (x*2) + (x+10)

a a -> b

(+) <$> (fmap (+)) f := (+).(*2) g := (+10)

(+)

r -> a r -> (a -> b) r -> a (*2)

(+) <$> (*2) <*> (+10) <=> \x -> (x*2) + (x+10)

ここに共通のパラメータxを注入するイメージ

\x -> f x (g x) <=> \x -> (x*2)+(x+10)

注)f <$> x = fmap f x

<*>

共通のパラメータxが与えられたものとして演算する

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

Open Cloud Campus21

第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 := (+).(*2) <=> f := \x w -> (x*2) + wg := x+10f <*> g <=> \x -> (x*2) + (x+10)

f := \w x -> (x*2) + wg := x+10g >>= f <=> \x -> (x*2) + (x+10)

同じ結果

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

Open Cloud Campus22

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

モナドとしての関数 「(->) r」

モナドにした恩恵として、do記法が使用できます。– Monadのバインド演算をすべて評価した後に、パラメータxを読んで結果を出すので、

「Readerモナド」と呼ばれます。

$ cat hoge.hsimport Control.Monad.Instances

addStuff :: Int -> IntaddStuff = do a <- (*2) b <- (+10) return (a+b)

$ ghci hoge.hs *Main> addStuff 319

addStuff = (*2) >>= \a -> ((+10) >>= \b -> retrun (a+b))<=> (*2) >>= \a -> ((+10) >>= \b _ -> (a+b))<=> (*2) >>= \a x -> a + (x+10)<=> \x -> (x*2) + (x+10)

instance Monad ((->) r) where return x = \_ -> x g >>= f = \x -> f (g x) x

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

14.3 計算の状態の正体

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

Open Cloud Campus24

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

状態付きの計算とは?

関数内部で「外部の状態」を想定することはできないので、外部の状態に依存した処理をする際は、「外部の状態」を引数として与えます。

– 例えば、関数randomで3つの乱数(Bool値)を取得するなら、次のようになります。 randomは、乱数の種(ジェネレータ)を取って、乱数と新しい種を返します。

これを一般化して、次の型の関数を「状態付きの計算」と呼びます。– sが「状態」で、aが状態に依存した「計算結果」

$ cat hoge.hsimport 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)

最初の種を作る関数

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

Open Cloud Campus25

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

状態付きの計算の例

「スタックの状態」を受けて、push/pop操作する関数の例です。

$ cat hoge.hstype 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])

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

Open Cloud Campus26

第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 stat

a

f a

f

gを実行する直前の状態 gを実行した直後の状態

gが保持する値

return x

x

return xは、「状態は変えずにxという値を持つ」モナド

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

Open Cloud Campus27

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

StateモナドでStackを実装

Stateモナドは、Control.Monad.Stateで定義されています。– 値コンストラクタが「state」(頭が小文字)なので注意。

$ cat hoge.hs import Control.Monad.State

type Stack = [Int]

pop :: State Stack Intpop = state $ \(x:xs) -> (x, xs)

push :: Int -> State Stack ()push a = state $ \xs -> ((), a:xs)

stackManip :: State Stack IntstackManip = do push 3 pop pop

$ ghci hoge.hs *Main> runState stackManip $ [5,8,2,1](5,[8,2,1])

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

Open Cloud Campus28

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

StateモナドでStackを実装

もう少し複雑な例$ cat hoge.hs import Control.Monad.State

type Stack = [Int]

pop :: State Stack Intpop = state $ \(x:xs) -> (x, xs)

push :: Int -> State Stack ()push a = state $ \xs -> ((), a:xs)

stackManip :: State Stack IntstackManip = do push 3 pop pop

stackStuff :: State Stack ()stackStuff = do a <- pop if a == 5 then push 5 else do push 3 push 8

$ ghci hoge.hs

*Main> runState stackStuff [9,0,2,1,0]((),[8,3,0,2,1,0])

*Main> runState stackStuff [5,0,2,1,0]((),[5,0,2,1,0])

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

Open Cloud Campus29

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

StateモナドでStackを実装

getとputで状態の確認と操作ができます。– getは、現在の状態を値にコピーします。– putは、現在の状態を指定の状態に強制リセットします。

push/popをget/putで実装した例

get = state $ \s -> (s, s)put stat = state $ \s -> ((), stat)

pop :: State Stack Intpop = do (x:xs) <- get put xs return x

push :: Int -> State Stack Intpush x = do xs <- get put (x:xs)

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

Open Cloud Campus30

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

Stateモナドの型の注意

モナドの類は、「*->*」(型パラメータを1つとって、初めて具体型になる)– 例えば、リストモナド [] は、[Int], [String], [Char]などさまざまな具体型を包括してい

ます。バインド演算を通して、[Int]を取って、[String]を返すなど、型を変換することが可能です。

Stateモナドの定義を見ると、「値」の型aが変換可能なパラメータに対応しています。「状態」の型sは、モナドの定義に固定的に埋め込まれているので、バインド演算で変換することはできません。

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

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

instance Monad [] where return x = [x] xs >>= f = concat (map f xs)

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

Open Cloud Campus31

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

乱数の連続生成をStateモナドで実施

乱数生成関数randomをStateモナドに包むと、乱数の種を「状態」として受け取ることができます。

$ cat hoge.hsimport System.Randomimport Control.Monad.State

randomSt :: (Random a) => State StdGen arandomSt = 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)

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

おまけ

StateモナドでWriterモナドを実装

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

Open Cloud Campus33

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

StateモナドとWriterモナドの比較

Stateモナドの定義を見ながら・・・

Writerモナドの定義を次のように書き直します。

よく似ています・・・。– Stateモナドは、状態(s)の初期値を受けて、各モナドがその値を変更していきます。一

方、Writerモナドは、ログ(モノイドw)の初期値を受けて、各モナドがログに追記していきます。

–つまり、Stateモナドの「状態」にモノイドを指定して、状態の変更操作を「追記」に限定すると、Writerモナドが得られると想像できます。

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

import Data.Monoidnewtype 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)))

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

Open Cloud Campus34

第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] IntlogNumber x = State $ \s -> (x, s `mappend` ["Got number: " ++ show x])

multWithLog :: State [String] IntmultWithLog = 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"])

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

Open Cloud Campus35

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

やってみました

Writerモナドの場合は、バインド演算に「ログの追加(mappend)」がハードコードされているので、tell, logNumberでは、追記するログを与えるだけです。

一方、Stateモナドの場合は、バインド演算時の状態変化は、ユーザが自由に実装できるので、明示的に`mappend`するように実装しています。

tell :: (Monoid w) => w -> State w ()tell log = State $ \s -> ((), s `mappend` log)

logNumber :: Int -> State [String] IntlogNumber x = State $ \s -> (x, s `mappend` ["Got number: " ++ show x])

tell :: (Monoid a) => a -> Writer a ()tell log = writer ((), log)

logNumber :: Int -> Writer [String] IntlogNumber x = writer (x, ["Got number: " ++ show x])

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

Open Cloud Campus36

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

Q&A

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

中井悦司Twitter @enakai00

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

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