Stateモナドのなにがうれしいのか
すごいH本.icon p.334の要約
まず、Stateモナドを使わずに、状態を扱うpopとpushを定義してみる
以下の関数では「Stackに何が積まれているか」は一種の状態だと考えることができる
Stack( = [Int])をある種の状態の例として使う
code:hs
-- Stackから一つ取り除き、取り除いたものと残りのStackを返す
pop :: Stack -> (Int, Stack)
pop (x:xs) = (x, xs)
-- 加えるものと、Stackを与えると、結果としての()と加えられたStackを返す
push :: Int -> Stack -> ((), Stack)
push a xs = ((), a:xs)
ここで以下のようなstackMainp関数を考える
code:hs
stackMainp :: Stack -> (Int, Stack)
stackMainp stack =
let ((), newStack1) = push 3 stack
(a , newStack2) = pop newStack1
in pop newStack2 -- popして、stackMainpの返り値として、値とスタックを返す
main = do
「3」をスタックに積んで、その後スタックから2つの値をpopする
popやpushの返り値の()やaは使わないけど、関数の仕様上、受け取っている。
こういった状態付きの計算に、手で状態を与え、いちいち名前を付けて受け取っている
冗長で、大変
純粋関数の世界でモナドを使わずに愚直にやるとこんな実装になるmrsekut.icon
Stateモナドを使うとこんなに簡素に書ける↓
doの中では、暗黙のstackに対してのアクションを実行している
code:hs
import Control.Monad.State
pop :: State Stack Int -- Stackを状態として持ち、Intを返す
pop = state $ \(x:xs) -> (x, xs) -- (x, xs)は(Int, Stack)
push :: Int -> State Stack ()
push a = state $ \xs -> ((), a:xs)
stackMainp :: State Stack Int
stackMainp = do
push 3
pop
pop
main = do
これで純粋関数の世界でも状態プログラミングができるようになる
手続き型のプログラミングをしているときは常時こんなコーディングをしている