
Thanks everyone for the helpful suggestions! Bryan Richer wrote:
what if the TVar is part of the state s? (StateT IO) can't give any guarantees about when or how the state is broadcast, but using STM within StateT actions still can.
I don't understand this, but it may go into the same direction as: Chris Smith wrote:
I had the same suggestion. However, you then no longer need StateT, as ReaderT is enough. Using a `ReaderT (TVar s) IO a` will allow atomic changes to the state s, along with interleaved IO when it's done safely rather than in the middle of a transaction.
ReaderT (TVar s) IO is interesting! It does not solve the problem of atomic modifications with side-effects, though. Currently I am implicity using ReaderT (TVar s), by explicitly passing the TVar around. In principle, for atomic modifications I have to get hold of a pure version (s -> s) of the modification. Maybe within a monadic context. But if determining this modification function is side-effectful, then it can not depend on the initial contents of the TVar! This leads me to the conclusion that either (a) atomic modification by a Kleisli map is impossible and another mechanism needs to ensure thread safety, or (b) we need to keep two versions of the state: one private, pure s and one public TVar s which we synchronize from time to time. The latter can be accomplished with atomic writes. More on that below. Isaac Elliott wrote:
What about creating a MonadState instance for the TVar? Very neat, but it would not solve the atomicity problem, since the update I have is not expressible as stateTVar.
YueCompl wrote:
I believe this is the use case for TMVar, use `takeTMVar` / `putTMVar` instead of `readTVar` / `writeTVar` will do.
TMVars appear non-empty for one thread only, as far as I understand. So any observer thread would be blocked while the worker thread is updating the state? Not good for my use case. The observer thread will be a webserver. I'd rather have the webserver report old states than block. I have neglected to mention one aspect of my problem that might be essential: All my state updates are monoidal. By that I mean that s is a monoid with s' <> s <> s = s' <> s and any effectful modification will be of the form f = \s -> (k s) >>= (\s' -> s' <> s) for some k :: s -> m s. This entails that instead of emitting modifications as functions I can emit modifications as values. emitModification :: (Monad m, Monoid s) => StateT s m a -> WriterT s (StateT s m) a emitModification (StateT f) = WriterT $ StateT $ \s -> do (a,s') <- f s return ((a,s'),s') broadcastModification :: (MonadIO m, Semigroup s) => TVar s -> (WriterT s m) a -> m a broadcastModification ref (WriterT m) = do (a,s') <- m (liftIO.atomically) (modifyTVar ref (\s -> s' <> s)) return a Then \ref -> broadcastModification ref . emitModification :: (MonadIO m, Monoid s) => TVar s -> StateT s m a -> StateT s m a This has the additional charm that I can explicitly control when writes to the TVar happen, by choosing which (StateT s m) blocks to enclose in this wrapper. I might even set up my communication as a TChan, if the observing thread maintains an own copy of state itself to merge the updates s' into. Olaf