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

State モナド

State モナドの使い方がもうひとつよく分からなかったので、State Monad のチュートリアルを読んでいたら、Parsec と同じ使い方をすれば良いのだと気がついた。

Parsec ではパーサのモナドをプログラムしても、それだけでは何もしてくれない。たとえば、Parsec の(プリミティブ)関数の letter は、次のように parser 関数の引数にして実行しないとなにもしない。

Prelude Text.Parsec> parse letter "" "hello"
Right 'h'

また、要素的 (primitive) な関数はパーサーコンビネータと組み合わせることによって複雑なパターンに対応することができる。

Prelude Text.Parsec> parse (many1 letter) "" "hello world"
Right "hello"

さらに、パーサ関数はモナドなので、do 記法で結合することができる。

Prelude Text.Parsec> parse (do x <- char 'h'; y <- char 'e'; return [x,y]) "" "hello"
Right "he"

同じような発想が、State モナドでも使える。

まず、State モナドの要素的な関数 (primitive) だが、return、put、get などがある。これらの関数は関数 runState の引数にすることで、状態を変化させ、runState はペア (値、状態)を戻り値として返す。

runState の型は次のようになる。

Prelude Control.Monad.State> :t runState
runState :: State s a -> s -> (a, s)

つまり State モナドと状態を引数にとり、(値、状態)のペアを戻り値として戻す。例えば要素的な State モナドである return を使ってみよう。return は単に値に引数を置くだけの動作をする。それでは return を使って 'X' を値として返し、状態は 1 を runState に渡してみる。

Prelude Control.Monad.State> runState (return 'X') 1
('X',1)

ちゃんと戻り値のペアの値のところに 'X' 状態のところに 1 が入れられて返る。このように、State モナドの分かりにくさは、return 単独では動作しないということだ。しかし、これも Parsec の使い方の類推で考えると途端にわかりやくすなる。

それでは、もう一つのプリミティブ get を調べてみよう。get は状態をとりだし、それを値にいれて返す。

Prelude Control.Monad.State> runState get 1
(1,1)

また、put は状態に引数をいれ、値は () でリセットする。

Prelude Control.Monad.State> runState (put 2) 1
((),2)

また、これらの要素的なモナドは do 記法の中で組み合わせて使うことができる。次のプログラムは状態に5をセットし、'X' を値として返す。

Prelude Control.Monad.State> runState (do put 5; return 'X') 1
('X',5)

次は状態を get で取り出し、その値に1を加えて状態に戻し、もとの状態を値として戻している。

Prelude Control.Monad.State> runState (do x <- get; put (x+1); return x) 1
(1,2)

また modify モナドは状態を操作し、gets モナドは状態から取り出した値を加工して値に入れる。

Prelude Control.Monad.State> runState (modify (+1)) 1
((),2)
Prelude Control.Monad.State> runState (gets (+1)) 1
(2,1)

さらに runState 関数と同じような使い方をするが、戻り値が値になる evalState と戻り値が状態になる execState がある。

Prelude Control.Monad.State> evalState (gets (+1)) 1
2
Prelude Control.Monad.State> execState (gets (+1)) 1
1

そこで、これらを使ってスタック操作をプログラムしてみた。

ファイル名: push.hs

import Control.Monad.State

push :: a -> State [a] a
push x = do xs <- get; put (x:xs); return x

pop :: State [a] a
pop = do xs <- get; put (tail xs); return (head xs)

実行例:

*Main> runState (do push 1; push 2; push 3) []
(3,[3,2,1])
*Main> runState (do push 1; push 2; push 3; pop; pop;) []
(2,[1])

ついでに、スタックを使って逆ポーランド記法で計算するプログラムを作ってみた。

ファイル名: calc.hs

import Control.Monad.State

push :: a -> State [a] a
push x = do xs <- get; put (x:xs); return x

pop :: State [a] a
pop = do xs <- get; put (tail xs); return (head xs)

poland "+" = do x <- pop; y <- pop; push (x + y)
poland "*" = do x <- pop; y <- pop; push (x * y)
poland x = push (read x :: Int)

calc xs = evalState (do mapM_ poland xs; pop) []

実行例:

*Main> calc ["1","2","*"]
2
*Main> calc ["1","2","+","3","*"]
9
by tnomura9 | 2012-01-02 09:18 | Haskell | Comments(0)
<< System.Console.... lex >>