Search code examples
performancehaskellmonadsstate-monadreader-monad

Is there any significant difference between StateT over Reader and ReaderT over State?


When I design my programming model I always have a dilemma which approach is better:

type MyMonad1 = StateT MyState (Reader Env)
type MyMonad2 = ReaderT Env (State MyState)

What are the benefits and trade offs between using one monad stack over another? Does it matter at all? What about performance?


Solution

  • In the general case, different orderings of monad transformers will lead to different behaviors, but as was pointed out in the comments, for the two orderings of "state" and "reader", we have the following isomorphisms up to newtypes:

    StateT MyState (Reader Env) a  ~  MyState -> Env -> (a, MyState)
    ReaderT Env (State MyState) a  ~  Env -> MyState -> (a, MyState)
    

    so the only difference is one of argument order, and these two monads are otherwise semantically equivalent.

    With respect to performance, it's hard to know for sure without benchmarking actual code. However, as one data point, if you consider the following monadic action:

    foo :: StateT Double (Reader Int) Int
    foo = do
      n <- ask
      modify (* fromIntegral n)
      gets floor
    

    then when compiled with GHC 8.6.4 using -O2, the newtypes are -- obviously -- optimized away, and this generates exactly the same Core if you change the signature to:

    foo :: ReaderT Int (State Double) Int
    

    except that the two arguments to foo get flipped. So, there's no performance difference at all, at least in this simple example.

    Stylistically, you might run into situations where one ordering leads to nicer looking code than the other, but usually there won't be much to choose between them. In particular, basic monadic actions like the one above will look exactly the same with either ordering.

    For no good reason, I tend to favor #2, mostly because the Env -> MyState -> (a, MyState) looks more natural to me.