Help us understand the problem. What is going on with this article?

【Control.Monad.Trans】(2) StateTモナド

Haskell/Monad transformers
A Gentle Introduction to Monad Transformers
モナドトランスフォーマー・ステップ・バイ・ステップ


【Control.Monad.Trans】(1) Identityモナド - Qiita
【Control.Monad.Trans】(2) StateTモナド - Qiita
【Control.Monad.Trans】(3) ExceptTモナド - Qiita
【Control.Monad.Trans】(4) ReaderTモナド - Qiita
【Control.Monad.Trans】(5) IOモナド - Qiita

Monad transformersを中心に、モナドの基本的な事項をまとめてみました。(1)~(5)までの連載で、Monad transformersを試行していきます。実際にはそれぞれの回で、Identity, StateT, ExceptT, ReaderT, IO モナドを積み重ねていく例題を示しています。

Monad transformersについては、以下のサイトが表向きは最新といわれるものでしょうが、中身はControl.Monad.Transの方をimportしたりしているので、説明の都合上、Control.Monad.Transを使います。
mtl - Hackage - Haskell

今回はStateTモナドです。

1. StateTモナドの定義

Control.Monad.Trans.State.Strict - Hackage - Haskell

m をbaseモナドとして、以下のようにStateT型を定義します。

newtype StateT s m a = StateT { runStateT :: s -> m (a,s) } 

モナドとしての定義です。baseモナド m のreturnや>>=を使って以下のように定義します。

instance (Monad m) => Monad (StateT s m) where
    return a = StateT $ \ s -> return (a, s)

    m >>= k  = StateT $ \ s -> do
        (a, s') <- runStateT m s
        runStateT (k a) s'

    fail str = StateT $ \ _ -> fail str

この定義からわかるように、m >>= k の m の値は a です。(*1)
failの定義の右辺のfailは、m=Identityの場合は、Identityモナドのfailです。

2. 補助関数 - state, get, put

StateTモナドは通常、グローバルな状態を管理するために使われます。アクセス関数としてgetとputが定義されます。stateは純粋な状態遷移関数をStateTモナドに変換するものです。

-- | Construct a state monad computation from a function.
-- (The inverse of 'runState'.)
state :: (Monad m)
      => (s -> (a, s))  -- ^pure state transformer
      -> StateT s m a   -- ^equivalent state-passing computation
state f = StateT (return . f)


-- | Fetch the current value of the state within the monad.
get :: (Monad m) => StateT s m s
get = state $ \ s -> (s, s)

-- | @'put' s@ sets the state within the monad to @s@.
put :: (Monad m) => s -> StateT s m ()
put s = state $ \ _ -> ((), s)

3. StateTモナドの使用例

簡単な例を2つ示します。その前に実行コマンドの簡略版を定義しておきます。

runState m = runIdentity . runStateT m

まずはgetとputの使用例です。

import Control.Monad.Trans.State.Strict
import Data.Functor.Identity

-- runState threeTicks 5

tick :: StateT Int Identity Int
tick = do n <- get
          put (n+1)
          return n

threeTicks :: StateT Int Identity Int
threeTicks = do n1<-tick
                n2<-tick
                n3<-tick
                return(n1+n2+n3)

以下のコマンドで実行します

*Main> runState threeTicks 5
(18,8)

次はStateTでスタックを実現します。stateを明示的に使う例となっています。

Control.Monad.Trans.State.Strict
import Data.Functor.Identity

-- runState tasu [8,7..0]

type Stack = [Int]

tasu :: StateT Stack Identity Int
tasu = do
  state $ \xs -> ((), 9:xs)       -- push 9
  a <- state $ \(x:xs) -> (x, xs) -- a <- pop
  b <- state $ \(x:xs) -> (x, xs) -- b <- pop
  return (a+b) 

ここで注目して欲しいのは、StateTモナドの値である a と b は、それぞれ \(x:xs) -> (x, xs) の x の値であることです。このことは上で述べた(*1)に対応しています。

それでは実行してみましょう。

*Main> runState tasu [8,7..0]
(17,[7,6,5,4,3,2,1,0])

今回は以上となります。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away