Cheatsheet: Stacking the State and Either Monads
I do this rarely enough that I always have to Google for how to do it, which is a good sign that I should just write it down.
Before running the code below, install mtl
. Running cabal install mtl
works.
Here, I’ll use Stored
as the type of the state stored in the state monad and Err
as the type of the error. I could have picked any types for these two.
import Control.Monad.State (StateT, modify, get, put, lift, evalStateT)
import Data.Map (Map)
import qualified Data.Map as Map
type Stored = Map String Int
type Err = String
type StoreM = StateT Stored (Either Err)
note :: a -> Maybe b -> Either a b
note msg = maybe (Left msg) Right
save :: String -> Int -> StoreM ()
save k v = modify (Map.insert k v)
load :: String -> StoreM Int
load k = do
store <- get
lift $ note ("the key " ++ k ++ " is missing") (Map.lookup k store)
stateManip :: StoreM Int
stateManip = do
save "x" 1
save "x" 2
save "y" 123
x <- load "x"
y <- load "y"
save "z" (x + y)
load "z"
main = putStrLn $ show $ evalStateT stateManip Map.empty
StoreM
is the monad type.
evalStateT
runs your monadic function (stateManip
) with some starting state (Map.empty
, in this case).
I’ve defined two other functions in that monad: save
and load
. The save
function modifies the state, and it always succeeds. The load
function reads the state (but doesn’t modify it), and may fail. Both of these are implemented in terms of functions provided by the monad library.
I use a helper function called note
to convert Nothing
into Left Err
by adding an error message, but this doesn’t really have much to do with the monad. It’s just for convenience.
Besides evalStateT
, the key functions from Control.Monad.State
are modify
(which modifies the state value by applying some function to it), get
(which gives you the current state), put
(which replaces the current state), and lift
(which “converts” a function that doesn’t use state into one that does).