Haskell: State and StateT examples.

A look at the State Monad and StateT Monad Transformer.

Beginning to work with Monads is an important aspect of learning to work with Haskell. I wanted to add my take on some simple examples to try and add to the existing ones. So let’s have a look.

Let’s say

We have a web app and we need to monitor if a user is logged in or not across our session, and each of our functions return a result.

The code

Firstly we need to import our monad libraries. For this example we will be using:

import Control.Monad.IO.Class (liftIO)  -- ^ liftIO allows us to lift the output
                                        -- out of the monad and for example
                                        -- print or read, just like normal IO.

import Control.Monad.Trans.State        -- ^ the bog standard State Monad,
                                        -- together with the StateT Monad
                                        -- Transformer, which allows us to
                                        -- enrich the IO monad with the
                                        -- capabilities of State monad.

OK, I will try and comment the text as I go along, but so far so good. Let’s now define our two data types that we will use, based on the above web app assumptions.

data WebsiteResult = OK | Err String       deriving (Show, Eq)
data WebsiteState = LoggedIn | LoggedOut   deriving (Show,Eq)

So we know that the Website can return an OK or an Err + String information and all the state we want to track is if we are LoggedIn or LoggedOut. I hope this is all good until now. Let’s now have a look at defining an operation.

exOperation :: State WebsiteState WebsiteResult
exOperation = do
  logIn "Password"

Arguably you can read what the exOperation does - without seeing the actual implementation (to which we will get to) of the operations. It first tries to login by providing a string "Password", then it attempts to make a transaction, and at the end it prints out the result. We can agree this is very neat. Furthermore if we look at the type of exOperation we can already get an insight into what it tells us. We read it as: we are operating with a State monad that is holding an instance of WebsiteState and at the end we are returning a WebsiteResult.

OK now let’s have a look at how we would go about implementing these operations.

logIn :: String -> State WebsiteState WebsiteResult
logIn password = do
  if password == "Password"  -- | check if it is the password 
    then put LoggedIn        -- | logged in
    else put LoggedOut       -- | logged out
  checkOk                    -- | at the end we check to see what happened

      checkOk :: State WebsiteState WebsiteResult
      checkOk = do
        state <- get
        if state == LoggedIn then return OK else return $ Err "Bad Log In"

The first one takes a String, compares it to the (albeit hard-coded here) correct password. If it likes it, it changes the state to LoggedIn by using the put function. If it doesn’t then it changes it to LoggedOut. At the end I provided one more monad instance to show how we would change the return. In it we use the other useful function for the State monad, namely get which gets the WebsiteState out of the monad. Hopefully you are starting to get the gist of it, if not let’s look further at the other two functions:

makeTransaction :: State WebsiteState Bool
makeTransaction = do
  state <- get
  case state of
    LoggedIn -> return True
    LoggedOut -> return False

printResult :: State WebsiteState WebsiteResult
printResult = do
  state <- get
  case state of
    LoggedIn -> pure $ OK
    LoggedOut -> pure $ Err "Hi, enter your good password!"

These are mostly identical with the exception of one thing, namely the use of pure instead of return. It’s worth keeping in mind that both are identical, and it’s mostly to do with defining Applicative Functor.

Running the Monad

OK so all good, but how do we get the result of running the exOperation monad? Well to do this we need to use runState :: State s a -> s -> (a, s) which (as its type suggests) takes a State Monad and an initial State and returns the result of the calculation as a tuple of state and result. Perfect so let’s do it.

result = runState exOperation LoggedOut -- will return (OK,LoggedIn)

And if we wanted to get the result only and print it we could simply feed this to a fst in a IO () monad.

printOutResult :: IO ()
printOutResult = print $ fst result

But what if …

But what if we wanted to print out results as we went along, would that be possible? The answer is yes, but that means that we would need access to an IO monad instance while we are inside the State monad, which still behaves as we would expect IO to behave. This is exactly what StateT monad is for. Where State would have to have a signature of the type State Type1 (IO Type2) we can see from the signature that we don’t have (easy) access to Type2 - as it is wrapped inside the IO and we cannot reach inside to grab it. StateT allows us to make use of a type looking like StateT Type1 IO Type2 - hence now we can make use of both the State functions and the IO capabilities. Let’s look at the example, which I have annotated to make it easier to follow along.

--  If we wanted to combine this behaviour with an IO Monad we would need to use
--  the StateT transformer.

operations :: IO ()
operations = do
  putStrLn "Hello and Welcome to our Operations.\n Please insert your password:"
  pass             <- getLine
  let computation  = logInT pass
  let initialState = LoggedOut
  result           <- runStateT computation initialState
  return ()

-- | Observe how our return actually returns inside the IO Monad but our state
-- is outside of it. This offers us more control than something like "State
-- WebsiteState (IO WebsiteResult)" because we get a shallower version of the
-- monad, and we also get access to the 

logInT :: String -> StateT WebsiteState IO WebsiteResult
logInT password = do
  -- | First thing we get the state
  state <- get
  -- | Then, we check if we are already logged in.
  if state == LoggedIn
    then liftIO $ print "Allready Logged In!"
    else liftIO $ print "You need to sign in"
  -- | Now we get to check the password.
  if password == "Password"           
    then put LoggedIn                 
    else put LoggedOut
  -- | In the end we get the state again to check if the log in was successful.
  state <- get
  case state of
    LoggedIn -> liftIO $ print "Correct Password! Welcome!"
    _ -> liftIO $ print "Bad Password. Denied."
  -- | And at the end end we just return a random Auth Code.
  return OK

Worth noting here is that we could have just used return or pure to lift the IO operation, but the issue here is that it will not print or read it (best way to understand this would be to try it yourself), hence we need to use liftIO - this way we make sure that the IO operations happen as expected.

In the end

Hopefully this example helps you use State and StateT and gives a bit more insight.

Other Resources