Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

State's `put` and `get` functions

Tags:

haskell

I'n looking at the State Monad's put and get:

ghci> :t get
get :: MonadState s m => m s

ghci> :t runState
runState :: State s a -> s -> (a, s)

ghci> runState get [1,2,3]
([1,2,3],[1,2,3])

In get's type signature: MonadState s m => m s, how does [1,2,3] have a type of MonadState s m? It's not clear to me what the types of s and m are.

Also, can you please say more as to how to use put?

ghci> :t put put :: MonadState s m => s -> m ()

Overall, it seems that I don't understand what MonadState s m is. Could you please explain with put and get examples?

like image 835
Kevin Meredith Avatar asked Jan 09 '23 23:01

Kevin Meredith


2 Answers

MonadState s m is a typeclass constraint, not a type. The signature:

get :: MonadState s m => m s

Says that for some monad m storing some state of type s, get is an action in m that returns a value of type s. This is pretty abstract, so let’s make it more concrete with the less-overloaded version of State from transformers:

get :: State s s
put :: s -> State s ()

Now say we want to use State to keep a simple counter. Let’s use execState instead of runState so we can just pay attention to the final value of the state. We can get the value of the counter:

> execState get 0
0

We can set the value of the counter using put:

> execState (put 1) 0
1

We can set the state multiple times:

> execState (do put 1; put 2) 0
2

And we can modify the state based on its current value:

> execState (do x <- get; put (x + 1)) 0
1

This combination of get and put is common enough to have its own name, modify:

> execState (do modify (+ 1)) 0
1

> execState (do modify (+ 2); modify (* 5)) 0
10

MonadState is the class of types that are monads with state. State is an instance of that class:

instance MonadState s (State s) where
  get = Control.Monad.Trans.State.get
  put = Control.Monad.Trans.State.put

So are StateT (the state monad transformer, which adds state to another monad) and various others. This overloading was introduced so that if you’re using a stack of monad transformers, you don’t need to explicitly lift operations between different transformers. If you’re not doing that, you can use the simpler operations from transformers.


Here’s another example of how to use State to encapsulate a map of variables with Data.Map:

import Control.Monad.Trans.State
import qualified Data.Map as M

action = do
  modify (M.insert "x" 2)        -- x = 2
  modify (M.insert "y" 3)        -- y = 3
  x <- gets (M.! "x")
  y <- gets (M.! "y")
  modify (M.insert "z" (x + y))  -- z = x + y
  modify (M.adjust (+ 2) "z")    -- z += 2
  gets (M.! "z")                 -- return z

main = do
  let (result, vars) = execState action M.empty

  putStr "Result: "
  print result

  putStr "Vars: "
  print vars
like image 160
Jon Purdy Avatar answered Jan 18 '23 21:01

Jon Purdy


In get's type signature: MonadState s m => m s, how does [1,2,3] have a type of MonadState s m? It's not clear to me what the types of s and m are.

ghci> runState get [1,2,3]

The function runState takes two arguments: the first is the State action to run, and the second is the initial state. So, since the initial state is [1,2,3] which is a list of integers (*), the state type s is just [Integer].

(*) Actually, [1,2,3] :: Num a => [a] before GHCi defaults it, but for simplicity's sake let's use [Integer] as GHCi does.

Hence, we see that runState is specialized to

runState :: State [Integer] a -> [Integer] -> (a, [Integer])

Now, about the first argument:

get :: MonadState s m => m s

We must have m s = State s a because we are passing it to runState which requires such type. Hence:

runState :: State [Integer] a -> [Integer] -> (a, [Integer])
get :: MonadState s m => m s
with m s = State [Integer] a

The latter equation can be simplified as follows:

runState :: State [Integer] a -> [Integer] -> (a, [Integer])
get :: MonadState s m => m s
with m = State [Integer]
and  s = a

Substituting s:

runState :: State [Integer] a -> [Integer] -> (a, [Integer])
get :: MonadState a m => m a
with m = State [Integer]

Substituting m:

runState :: State [Integer] a -> [Integer] -> (a, [Integer])
get :: MonadState a (State [Integer]) => State [Integer] a

Now, the constraint MonadState a (State [Integer]) is satisfied only when a = [Integer]. This is tricky to see, since the MonasState type class exploits a functional dependency to enforce that every monad in that class has only one related state type. This is also made more complex from State being a wrapper around StateT. Anyway, we get:

runState :: State [Integer] a -> [Integer] -> (a, [Integer])
get :: MonadState a (State [Integer]) => State [Integer] a
with a = [Integer]

So,

runState :: State [Integer] [Integer] -> [Integer] -> ([Integer], [Integer])
get :: MonadState [Integer] (State [Integer]) => State [Integer] [Integer]

And since the constraint is satisfied:

runState :: State [Integer] [Integer] -> [Integer] -> ([Integer], [Integer])
get :: State [Integer] [Integer]

And now we can see the involved ground types.

like image 33
chi Avatar answered Jan 18 '23 23:01

chi