Stateモナド
状態を扱うためのMonad型クラス
IOモナドやSTモナドのオープンな一般化のようなもの
IOモナドなどは真の実装が処理系の内部にあるので中身が見れない
しかし、Stateモナドは内部実装もすべて見れるのでその点で理解がしやすい
自分で再定義できる
IOモナドもWorld -> (a, World)の様なイメージなのでそれを一般化したものがStateモナドだと考えることができる
Stateモナドのなにがうれしいのか
docs
型の定義
code:hs
newtype State s a = State { runState :: s -> (a, s) }
s: 状態を表す型。型変数のままでも、具体的な型でも良い
a: Stateモナドの中に含まれる値の型
「状態」の世界から、Stateモナドから取り出したときの型
モナド定義側関数では気にすることはない
モナド使用側関数が欲しい型
s -> (a, s)は、前の状態 -> (値, 次の状態)
Maybeとかを考えているときは、
「モナド値」と言えば単にMaybe Intを想像しとけば良かったが、
Stateモナドではモナド値はState (s -> (a, s))になると言っている
↓のreturnの定義を見れば分かる通り。
定義
code:hs
instance Monad (State s) where
return a = State $ \s -> (a,s)
(State x) >>= f = State $ \s -> -- ①
let (a,s') = x s -- ②
in runState (f a) s' -- ③
returnの方を見てみる
\前の状態 -> (a,前の状態)という風に、特に状態を操作せずにそのまま返しているだけ
bindの方を見てみる
具体的な型は、(>>=) :: State s a -> (a -> State s b) -> State s b
①の\s ->が前の状態
②で、前の状態に対して、(新しい状態,計算結果)を得る
③で、新しい状態に対して、runState
普通はそうしないが、2回runStateを使って、以下のようにも書ける
code:hs
st >>= f = State $ \s ->
let (s', a) = runState st s
in runState (f a) s'
関数
state :: (s -> (a, s)) -> State s a
内部関数からStateモナドを作る関数
code:hs
import Control.Monad.State
main = do
let st = state $ \s -> (1, s)
print $ runState st ()
runState: : State s a -> s -> (a, s)
Stateモナドから値を取り出す
戻り値は(値, 状態)
code:hs
import Control.Monad.State
main = do
let a = return 1 :: State s Int
print $ runState a () -- ()は初期状態(必須)
evalState:: State s a -> s -> a
値だけを取り出す
execState:: State s a -> s -> s
状態だけを取り出す
Stateアクション
https://hackage.haskell.org/package/mtl-2.3/docs/Control-Monad-State-Class.html
get:: State s s
状態を読み取る
put:: s -> State s ()
状態を書き換える
modify:: (s -> s) -> State s ()
状態に関数を適用する
code:hs
import Control.Monad.State
test = do -- 状態 -> (値, 状態)
a <- get -- 状態を取得
put $ a + 1 -- 状態+1を新しい状態に設定
modify (* 2) -- 状態に関数を適用
return a -- 最初の状態を値として返す
main = do
print $ runState test 5
-- 結果: (5, 12)
Recordのstateをputで更新する
code:hs
import Control.Monad.State
import Data.Functor.Identity
data User = User { _id :: Int, _name :: String } deriving (Show)
updateId :: StateT User Identity ()
updateId = do
user <- get
let id = _id user
put $ user { _id = id + 1 } -- こうやって更新する
main :: IO ()
main = print $ runIdentity $ runStateT updateId (User 1 "Kota")
Elmと同じだね
参考
すごいH本 p.335
Haskell 状態系モナド 超入門 - Qiita
https://qiita.com/lotz/items/503ef04b03433d29f77c
Lensの話
https://zenn.dev/lotz/articles/8d9af0eb45a229bf3c00
https://qtamaki.hatenablog.com/entry/2014/12/23/000515
https://kowainik.github.io/posts/2018-11-18-state-pattern-matching
https://xtech.nikkei.com/it/article/COLUMN/20070109/258229/