あけまして、おめでとうございます
ミソカから元旦にかけて、あるチュー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の理解力をさらに深めたいと思います。
0 件のコメント:
コメントを投稿