Stateモナドについてメモ
Stateモナドについて、自分なりにまとめる。自分用のメモなので詳しくしりたい人は下の参考リンクなどをみるほうがよいと思う。
モナドとは
モナドについては箱にたとえる方法や、関数型言語で副作用をあつかうための仕組み、などいろいろな説明がある1。使ってみて感覚をつかむのがいいと思う。自分としてはコンテキストをあつかう、という説明がしっくりきた。そのうち自分の言葉で説明するエントリも書く予定。
モナドのインスタンスとするには return
と bind (>>=)
を定義する必要がある。
Stateモナドとは
変数の書き換えを行わない純粋な関数型言語で状態を扱うための仕組み。
どうやって副作用をともなわずに状態をあつかっているのかというと、基本的には「前の状態」を入力として「次の状態」を出力とする(多分厳密には違う)
モナドを使うことで手続的に状態を変更するように書くことができる。
State s a
のような型となり、s
は状態の型、a
は最終的な値の型となる。この順番をいつも忘れてしまうのだが、型があるのでわかりやすいともいえる。
現状のmtlパッケージの実装とは違うが、Monadのインスタンス化はだいたい以下のようにして行える。
newtype State state a = State { runState :: state -> (a, state) }
instance Monad (State state) where
return a = State (\s -> (a, s))
State x >>= f = State (\s -> let (a, s') = x s in runState (f a) s')
例
公式ドキュメントから引用 n + x
をStateモナドを使って実装している。
tick :: State Int Int
= do n <- get
tick +1)
put (nreturn n
plusOne :: Int -> Int
= execState tick n
plusOne n
plus :: Int -> Int -> Int
= execState (sequence $ replicate n tick) x plus n x
基本的な操作
get
Stateから状態をとりだしてくる
put
Stateの状態を更新する
modify
get + put
実行系の操作
Monad系はよくrunXXX
のような関数をもち、手続的な書き方で処理を積んでおき、runXXX
で実際に実行、という流れになる。Stateモナドは状態と値をもつので最終的にそれぞれを返す操作もある。
runState
(値,状態)のタプルを返す。
runState :: (State s a) -> s -> (a, s)
execState
値はすてて状態を返す。
execState :: (State s a) -> s -> s
evalState
状態はすてて値を返す。
evalState :: (State s a) -> s -> a
無限の猿定理
最近流行っているっぽい?無限の猿定理2の一種、「ズン」と「ドコ」をランダムに繰り返し、「ズンズンズンズンドコ」になったら停止して「キヨシ」と表示するプログラムもStateモナドを使って書ける。 「ズン」の回数をカウントするのにStateモナドを使った。
module Main where
import Control.Monad.State
import System.Random
data ZundokoCount = Start | Zun1 | Zun2 | Zun3 | Zun4 | Doko deriving (Show)
count :: String -> ZundokoCount -> ZundokoCount
"ズン" Start = Zun1
count "ズン" Zun1 = Zun2
count "ズン" Zun2 = Zun3
count "ズン" Zun3 = Zun4
count "ドコ" Zun4 = Doko
count Doko = Doko
count _ = Start
count _ _
zundoko :: StdGen -> StateT ZundokoCount IO ZundokoCount
= do
zundoko gen <- get -- 状態の取り出し
z let (r, genN) = randomR (0,1) gen :: (Int, StdGen)
= if r == 0 then "ズン" else "ドコ"
str = count str z -- 次の状態
z' $ putStr str
liftIO -- 状態の更新
put z' case z' of
Doko -> return z -- Dokoだったらloop終了
-> zundoko genN
_
main :: IO ()
= do
main <- newStdGen
gen0 Start
runStateT (zundoko gen0) putStrLn " "
putStrLn "キヨシ"
$ stack runghc Zundoko.hs
ズンズンズンズンズンドコズンズンドコドコズンズンズンズンズンドコズンドコ
ドコズンズンドコドコドコズンズンドコズンドコドコドコズンドコドコドコズン
ドコズンズンドコズンドコズンズンズンズンドコ
キヨシ
参考
- Control.Monad.State.Lazyドキュメント
- Haskell Wiki - State Monad
- Haskell 状態系モナド 超入門
- Stateモナドが便利に使えた!
- 関数プログラミング実践入門