hliu
hliu

Reputation: 1047

How to modify a state monad?

I use State Monad Transformer to manage global state like this

data State = State ...
StateT State IO ()

And I use amqp to consume messages from RabbitMQ. The state will be modified according to messages received. The function has the type like

consumeMsgs :: Channel 
            -> Text 
            -> Ack 
            -> ((Message, Envelope) -> IO ())  -- ^ the callback function
            -> IO ConsumerTag

Right now we can ignore other parameters but the third which is a callback function I will supply and where the modification happen.

Because it's a mainly IO Monad, so I use this function as follows

consumeMsgs chan queue Rmq.Ack (flip evalStateT ssss . rmqCallback)

Here the ssss is the state I put in and I find that during the process of my callback function rmqCallback the state can be correctly modified. But, every time next callback happens the global state is the same as before the consumeMsgs is called or equal with ssss.

I understand State Monad is just a process needing an initial state to put in and maintain the state during whole way but has nothing to do with the state out of Monad (am I missing something?), so I count on MVar to hold and modify the state, and that works. I want to know it's there other way to handle this, maybe another Monad?

Upvotes: 3

Views: 512

Answers (2)

max630
max630

Reputation: 9238

It looks like you could use Network.AMQP.Lifted.consumeMsgs. StateT s IO is an instance of MonadBaseControl IO m, so you could run whole consumeMsgs inside single runStateT

Yes, StateT monad transformer is basically a nice notation for a pure code, so if your API accepts only IO callbacks you have no choice but to use "real" state like MVar or IORef etc.

PS: As other answer suggests, the state changes done in Network.AMQP.Lifted.consumeMsgs's callback do not propagate to subsequent callback runs or resulting state. I cannot wrap my head around the implementation, but I tried liftBaseWith a bit and it really looks like so.

Upvotes: 1

Vora
Vora

Reputation: 301

To add a clarification that might be useful for future reference, the accepted answer is not exact. While Network.AMQP.Lifted.consumeMsgs should work with StateT s IO, the RabbitMQ haskell library actually discards the monadic state after each use. This means that if you do use that instance, you will not see changes made after the initial consumeMsgs call, including changes made by the callback itself. The callback is basically called with the same Monadic state every time - the state in which it was when the callback was registered.

This means that you can use it to pass global configuration state, but not to keep track of state between callback executions.

Upvotes: 1

Related Questions