Haskell 入門


State モナド

State モナド

モナドを使いこなす練習には State モナドの使い方を学習するのが一番だ。ghci 上で State モナドを使うためには Control.Monad.State をインポートする必要がある。State モナドはモナドなので、IO モナドと同じく return 関数と >>= 演算子が使える。また、State モナドの特徴を知るには get と put と runState という3つの関数を使ってみるだけでいい。

Prelude> :m Control.Monad.State
Prelude Control.Monad.State> :t get
get :: MonadState s m => m s
Prelude Control.Monad.State> :t put
put :: MonadState s m => s -> m ()
Prelude Control.Monad.State> :t runState
runState :: State s a -> s -> (a, s)

runState

State モナドには IO モナドにはない特徴がある。それは、State モナドで書かれたプログラムはそのままでは動かず、runState の引数として与えられて初めて動作するということだ。このことは、State モナドの do ブロックのプログラムが Haskell の中の別言語として働く様に見える。モナドのプログラムを扱うときにこの観点からモナドを見ることはプログラムの見通しをわかりやすくする。

例えば return だけを使った関数は、State モナドだ。しかし、これを動作させるためには runState 関数が必要だ。runState 関数は第1引数に State モナドをとり、第2引数に状態のデータをとる。

Prelude Control.Monad.State> runState (return 10) "State Data"
(10,"State Data")

また、runState の値はモナドのコンテナの値(10)と状態のデータ "State Data" のペアだ。

get と put

状態はプログラムの進行と共に値が変わる。このような副作用のある値を純粋関数では記述できないが、State モナドではモナドのプログラムとすることでそれを可能にしている。関数 get は状態の値を取り出し、関数 put は State モナドのコンテナの値を状態に保存する。

Prelude Control.Monad.State> runState (get) "State Data"
("State Data","State Data")
Prelude Control.Monad.State> runState (put "Monad Data") "State Data"
((),"Monad Data")

上のプログラムでは get で状態の "State Data" が読み込まれてモナド値として runState の値のペアの fst 要素として返されている。また、put 関数では引数の "State Data" が状態に書き込まれる。

状態はこのようにモナドによって書き換え可能な変数を提供するが、それは State モナドのプログラムからは隠された変数になる。State モナドのプログラムには状態を表す変数は現れない。ただ get と put を介して読み書きすることができるだけだ。

State モナドによるスタックの実現

手続き型言語にあってはスタックは重要なデータ構造だ。しかし、Haskell では動的にデータを書き換えることができない。そこで、State モナドを使うことによってそれを実装してみる。

State を利用したスタックをプログラムするために、State モナドによって push と pop を定義できる。

Prelude Control.Monad.State> push x = do st <- get; put (x:st)
 :: State [Int] ()
Prelude Control.Monad.State> pop = do st <- get; put (tail st);
 return (head st) :: State [Int] Int
Prelude Control.Monad.State> runState (do push 10; pop) []
(10,[])
Prelude Control.Monad.State> runState (do push 10; push 20; pop) []
(20,[10])