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

1,535 views

Published on

Published in: Technology, Business
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,535
On SlideShare
0
From Embeds
0
Number of Embeds
59
Actions
Shares
0
Downloads
18
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

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

  1. 1. オープンクラウド・キャンパス 第14章 もうちょっとだけモナド 中井悦司 Twitter @enakai00
  2. 2. 14.1 Writer? 中の人なんていません!
  3. 3. 第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
  4. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 第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. 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
  16. 16. 第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
  17. 17. 第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
  18. 18. 14.2 Reader? それはあなたです!
  19. 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. 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. 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. 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
  23. 23. 14.3 計算の状態の正体
  24. 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. 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. 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. 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. 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. 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. 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. 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
  32. 32. おまけ StateモナドでWriterモナドを実装
  33. 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. 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. 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
  36. 36. 第14章 もうちょっとだけモナド Q&A 36 Open Cloud Campus
  37. 37. オープンクラウド・キャンパス すごいHaskell たのしく学ぼう! 中井悦司 Twitter @enakai00

×