あけまして、おめでとうございます
ミソカから元旦にかけて、あるチューhighまーのまま、stack overflowをおこし、体ダルダルで飲みつづけてます、本年もよろしくお願いします。
まず、今年の抱負
理由は聞かないでください
- 一番搾りは350ml缶2本まで
- 焼酎は薄めます
- 「セイッ」は月に一度にする
今年も頑張るぞ!
もなどとらんすふぉーまーってなんだよ
それは、よく分かってません、とりあえずモナドを合成することらしいですが、やってみました、あってるかどうかは分かりませんが、動くのでいいでしょう。
まず、普通のStateもなど
よくみるサンプル、Identityは恒等モナドっていって、関数で言うidみたいなもんだと思ってて、それのモナド用らしい。
type Stack = [Int]
push :: Int -> StateT Stack Identity ()
push x = state $ \xs -> ((), x:xs)
pop :: StateT Stack Identity Int
pop = state $ \(x:xs) -> (x, xs)
ghciで
*Main> f = do {push 5; pop; pop;}
*Main> runIdentity (runStateT f [1,2,3])
(1,[2,3])
*Main>
runStateTでStateTモナドを走らせたら、Identity aが帰ってくるので、更にrunIdentityで剥がしているだけ。
エラーの時どうする?
こうやってpopしまくると、エラーになる
*Main> f = do {push 5; pop; pop; pop; pop; pop;}
*Main> runIdentity (runStateT f [1,2,3])
*** Exception: /home/cuomo/Code/haskell/MonadTransformer/MonadStack.hs:18:15-32: Non-exhaustive patterns in lambda
*Main>
これは、\(x:xs)のパターンマッチが腐って、エラーになる、これはエラーとして処理したい、そんな時はpopをお利口チャンににして
push2 :: Int -> ExceptT String (StateT Stack Identity) ()
push2 x = state $ \xs -> ((), x:xs)
pop2 :: ExceptT String (StateT Stack Identity) Int
pop2 = do
s <- get
case s of
x:xs -> put xs >> return x
otherwise -> throwError "Stack is empty"
元々の機能にエラー処理を追加したことが、前のpopと違うところ、これはこうやって使う
*Main> f = do {push2 5; pop2; pop2; pop2; pop2; pop2;}
*Main> runIdentity (runStateT (runExceptT f) [1,2,3,4,5,6,7])
(Right 4,[5,6,7])
*Main> runIdentity (runStateT (runExceptT f) [1,2,3])
(Left "Stack is empty",[])
*Main>
スタックから取るのが無いのに、popすると結果にLeft値がはいって失敗が分かるようになる、タプルの中にLeft値と腐った空の配列入れてきてもらっても、無意味っぽいので、別の書き方で...
push3 :: Int -> StateT Stack (ExceptT String Identity) ()
push3 x = state $ \xs -> ((), x:xs)
pop3 :: StateT Stack (ExceptT String Identity) Int
pop3 = do
s <- get
case s of
x:xs -> put xs >> return x
otherwise -> throwError "Stack is empty"
StateTとExceptTの位置を変えて逆にすると、結果がRight値、またはLeft値で出てくるようになる。
*Main> f = do {pop3; pop3; pop3; pop3; pop3; pop3;}
*Main> runIdentity (runExceptT (runStateT f [1,2,3,4,5,6,7]))
Right (6,[7])
*Main> f = do {pop3; pop3; pop3; pop3; pop3; pop3; pop3; pop3}
*Main> runIdentity (runExceptT (runStateT f [1,2,3,4,5,6,7]))
Left "Stack is empty"
*Main>
成功すれば、Right値に結果がくるまれて、失敗した場合、Left値にエラーの文字列が入ってくるようになる、こっちの方がウケが良さそうな気がする。
操作をログでとる
さらにWriterTで何を突っ込んで、何を取り出したのをログする機能を追加してみる
type Log = [String]
push4 :: Int -> StateT Stack (WriterT Log (ExceptT String Identity)) ()
push4 x = do
tell ["push " ++ show x]
state $ \xs -> ((), x:xs)
pop4 :: StateT Stack (WriterT Log (ExceptT String Identity)) Int
pop4 = do
s <- get
case s of
x:xs -> do
tell ["pop " ++ show x]
put xs >> return x
otherwise -> do
throwError $ "Stack is empty"
同じように...
*Main> runIdentity (runExceptT (runWriterT (runStateT f [1,2,3,4,5,6,7])))
Right ((4,[5,6,7]),["push 5","pop 5","pop 1","push 10","pop 10","pop 2","pop 3","pop 4"])
*Main> runIdentity (runExceptT (runWriterT (runStateT f [1,2,3])))
Left "Stack is empty"
*Main>
外側のタプルの右側に操作したログが追加されるようになる。
最後にIOは
じゃぁ、この中でIOの処理入れたいんだど、どうすればいい? って疑問がわくよね、ここで、Identity a が効いてくる、IOとIdentityってkindすると
Prelude> :m +Control.Monad.Identity
Prelude Control.Monad.Identity> :k Identity
Identity :: * -> *
Prelude Control.Monad.Identity> :k IO
IO :: * -> *
Prelude Control.Monad.Identity>
にてるよね、そう、IdentityのところへIOを入れられるようになって、liftIOでリフトしてIOな関数を利用する。
push5 :: Int -> StateT Stack (WriterT Log (ExceptT String IO)) ()
push5 x = do
liftIO $ putStrLn "pushしまーすぅ"
tell ["push " ++ show x]
state $ \xs -> ((), x:xs)
pop5 :: StateT Stack (WriterT Log (ExceptT String IO)) Int
pop5 = do
s <- get
liftIO $ putStrLn "popすんぞ"
case s of
x:xs -> do
tell ["pop " ++ show x]
put xs >> return x
otherwise -> do
throwError $ show "Stack is empty"
これで、main()から呼んでみると
main :: IO ()
main = do
v <- runExceptT (runWriterT (runStateT stackIjily [1,2,3,4,5,6,7]))
putStrLn $ show v
stackIjily :: StateT Stack (WriterT Log (ExceptT String IO)) Int
stackIjily = do
push5 5
pop5
pop5
push5 10
pop5
pop5
pop5
pop5
pop5
x <- pop5
return x
こうやると、関数の中でIOを発行することができる。
~/Code/haskell/MonadTransformer $ runghc MonadStack.hs
pushしまーすぅ
popすんぞ
popすんぞ
pushしまーすぅ
popすんぞ
popすんぞ
popすんぞ
popすんぞ
popすんぞ
popすんぞ
Right ((6,[7]),["push 5","pop 5","pop 1","push 10","pop 10","pop 2","pop 3","pop 4","pop 5","pop 6"])
こうやって、モナドを積んでいけば、既存の処理を壊すこと無く機能を追加できる、ただ、積み上げる順序によって剥がし方がかわるのと、ちょっと複雑になると分かりにくくなってしまうような気がする。まぁ慣れればそんなに気にする事でもなさそうな。
その他の、同じようなやつ
で、同じような理由で、RWSモナドとか、MonadWriterとかMonadStateとかMonadReaderとかあるみたいだけど、使ったこと無いです、こんどやってみます。
今年は、haskellの理解力をさらに深めたいと思います。