Search code examples
haskellfunctional-programmingstate-monadio-monadreader-monad

To keep state in a function of type a -> ReaderT r IO b, is my only option to put an IORef in the closure? Or can I somehow use StateT?


Say I have to implement a function

f :: Foo -> ReaderT Bar IO Baz

that I have to pass to a consumer (i.e. I'll call c f) where Foo/Bar/Baz are imposed the consumer of the function, and that the consumer will call that function repeatedly with different Foos.

Do I have any chance of keeping some local state between successive calls to f?


In some way, I've already achieved that via an IORef, by changing the signature of f

f :: IORef S -> Foo -> ReaderT Client IO a

and partially applying it to some state I initialize beforehand:

s <- initState :: IO (IORef S)
c $ f s

This way, the fully applied f can alter the state in the IO monad via atomicModifyIORef' and some update function:

f s x = do
  liftIO $ atomicModifyIORef' s u
  -- ...

However, the solution above came natural for me because that state is truly a global mutable state that is modified not just by calls to f, but also by some other part of the program that runs concurrently with f.

But now I'm in need of f to have some private state that is not meant to be modified by anyone else. This makes me think of the StateT transformer, but I don't really know I can apply it in this case.


The practical usecase at hand is that I want a stateful notify function in the context of implementing a notification server. See here for a toy example of the implementation. In this scenario, Foo -> ReaderT Bar IO Baz is actually MethodCall -> ReaderT Client IO Reply (ReaderT Client IO a is DBusR a).

As you can see, whatever I do with notify, I must end up passing an MethodCall -> DBusR Reply function to export the client, and that function will be called multiple times, and is expected to return everytime, so it appears to me that the only way to keep state in it is its closure, i.e. I have to provide notify one more argument before MethodCall, and partially apply it to the initial state, as I explained above. And the only way to have that state change every time a MethodCall is passed, is to have the first additional argument be a truly mutable state, e.g. the IORef I mentioned above.

Is this it?


Solution

  • Imagine that the signature of f was:

    f :: Foo -> Reader Bar Baz
    

    and you somehow "used StateT" in f to thread a local state through a sequence of calls to f. That would mean that two consecutive calls to f with the same Foo argument with the resulting reader run using the same Bar argument:

    let baz1 = runReader (f foo) bar
        baz2 = runReader (f foo) bar
    in ...
    

    could result in two different baz1 and baz2 results, depending on the change in local state, right? In other words, it would be a violation of referential transparency.

    So, the only reason you can carry a state through f :: Foo -> ReaderT Bar IO Baz is because you have its base monad IO available, so any solution you come up with is going to have to use IO effects to maintain the state.

    However, it's easy enough to use StateT within the definition of f, while actually maintaining the state as an IORef that's private to f. If you write your core f logic using StateT:

    f :: Foo -> StateT S (ReaderT Client IO) a
    f foo = do ...
    

    you can build a sort of f-factory:

    f_factory :: IO (Foo -> ReaderT Client IO a)
    f_factory = do
      sref <- newIORef initS
      pure $ \foo -> do
        s <- liftIO $ readIORef sref
        (r, s') <- runStateT (f foo) s
        liftIO $ writeIORef sref s'
        pure r
    

    and in IO, you can write:

    f_with_state <- f_factory
    c f_with_state    -- pass to consumer
    

    Note that, since the sref was created within f_factory, it is included in the resulting closure f_with_state but not available anywhere else. And, in the core definition of f, you can just use get, put, modify, etc., without worrying about the fact that the actual f_with_state function is restoring and saving the state in the private sref IORef.