Stuck in state monad

I want to create a graph structure using IntMap nodes and unique keys. This topic has been highlighted here and here . I understand how the state monad works, basically wrapping the state → (val, state) function in newtype so that we can create an instance of monad for it. I read quite a bit about this topic. I still can’t understand how I can get unique (or just incremental) values ​​during the execution of my program. It’s easy enough to get the run of consecutive identifiers, but as soon as I “runState” to exit the monad, it seems that I returned to where I started to track the current identifier. I feel like I'm stuck in a monad. Another option I considered was to keep the entire IntMap and the current “next” ID as state, but this seems very “imperative” and extreme. This question is very similar, but did not get many answers (or maybe I just lack something obvious). What is the idiomatic way to use the state monad to get a unique identifier during program execution? Thanks.

+7
source share
1 answer

Suppose we had an IO -ify monad State . How will it look like? Our pure State monad is just a new type:

 s -> (a, s) 

Well, the IO version can fix side effects a bit before returning final values ​​that would look like this:

 s -> IO (a, s) 

This template is so common that it has a name, in particular StateT :

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

The name has a T at the end, because it is the T ransformer monad. We call m "base monad" and StateT sm "converted" monad.

StateT sm is only a Monad if m is Monad :

 instance (Monad m) => Monad (StateT sm) where {- great exercise -} 

However, in addition, all monad transformers implement the MonadTrans class, defined as follows:

 class MonadTrans t where lift :: (Monad m) => ma -> tma instance MonadTrans (StateT s) where {- great exercise -} 

If T is StateT s , then the lift type specializes in:

 lift :: ma -> StateT sma 

In other words, this allows us to “raise” an action in the base monad to become an action in the transformed monad.

So, for your specific problem, you need the StateT (IntMap kv) IO monad, which extends the IO an additional State . Then you can write your entire program in this monad:

 main = flip runStateT (initialState :: IntMap kv) $ do m <- get -- retrieve the map lift $ print m -- lift an IO action (k, v) <- lift readLn put (insert kvm) 

Note that I'm still using get and put . This is because the transformers package implements all the concepts I have described and generalizes the get and put signatures as:

 get :: (Monad m) => StateT sms put :: (Monad m) => s -> StateT sm () 

This means that they automatically work inside StateT . transformers , it simply defines State as:

 type State s = StateT s Identity 

This means that you can use get and put for State and StateT .

To learn more about monad transformers, I highly recommend Monad Transformers - step by step .

+10
source

All Articles