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 over another? Does it matter at all? How about performance?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With