人気ブログランキング | 話題のタグを見る

State モナドとモナド型関数

モナドのプログラミングにおいて、>>= 演算子の左右の項には厳しい制限がある。つまり >>= の左項は M a 型のモナド値であり、>>= の右項は a -> M a 型の関数でなくてはならない。このあたりの説明をスタックの pop 関数と push 関数を例にとってやってみる。

Prelude> import Control.Monad.State
Prelude Control.Monad.State> :{
Prelude Control.Monad.State| pop :: State [a] a
Prelude Control.Monad.State| pop = state (\s -> (head s, tail s))
Prelude Control.Monad.State| :}
Prelude Control.Monad.State> runState pop [1,2,3]
(1,[2,3])

つぎに、push 関数だが、これはスタックに積む値を引数に取るので型は push :: a -> State [a] a だ。

Prelude Control.Monad.State> :{
Prelude Control.Monad.State| push :: a -> State [a] a
Prelude Control.Monad.State| push x = state (\s -> (x, (x:s)))
Prelude Control.Monad.State| :}
Prelude Control.Monad.State> runState (push 5) [1,2,3]
(5,[5,1,2,3])

この push と pop を使ってスタックの先頭の要素を 10 倍にしてみる

Prelude Control.Monad.State> runState (pop >>= \x -> return (10*x) >>= push) [1,2,3]
(10,[10,2,3])

これは do 記法で記述したほうがわかりやすい。

Prelude Control.Monad.State> runState (do x <- pop; push (x * 10)) [1,2,3]
(10,[10,2,3])

>>= 演算子の右項に置くべき a -> State s a 型の関数の作り方をやってみた。やや技巧的だが、これがわかれば、State モナドを自由に操ることができるようになる。

State モナドのプログラミングでは上に述べた state 関数で関数をラッピングする方法より、return, get, put, modify 関数などを使って do 記法でプログラムする方法もある。むしろ、こちらのほうがわかりやすいかもしれない。

Prelude Control.Monad.State> :{
Prelude Control.Monad.State| pop :: State [a] a
Prelude Control.Monad.State| pop = do xs <- get; put (tail xs); return (head xs)
Prelude Control.Monad.State| :}

Prelude Control.Monad.State> runState pop [1,2,3]
(1,[2,3])

Prelude Control.Monad.State> :{
Prelude Control.Monad.State| push :: a -> State [a] a
Prelude Control.Monad.State| push x = do xs <- get; put (x:xs); return x
Prelude Control.Monad.State| :}

Prelude Control.Monad.State> runState (push 5) [1,2,3]
(5,[5,1,2,3])

Prelude Control.Monad.State> runState (do x <- pop; push (x*10)) [1,2,3]
(10,[10,2,3])

get, put 関数の動作は汎用的なので、State モナドのプログラミングはこちらを使うほうが王道だろう。


by tnomura9 | 2018-11-11 20:50 | Haskell | Comments(0)
<< List モナド 状態付き計算と State モナド >>