StateT over Reader 和 ReaderT over State 之间有什么显着差异吗?

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

当我设计我的编程模型时,我总是为哪种方法更好而进退两难:

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

使用一个 monad 与使用另一个 monad 之间有什么好处和权衡?这有关系吗?性能如何?

在一般情况下,monad 转换器的不同排序会导致不同的行为,但正如评论中指出的那样,对于 "state" 和 "reader" 的两个排序,我们有以下同构到新类型:

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

所以唯一的区别是参数顺序之一,这两个 monad 在语义上是等价的。

关于性能,如果不对实际代码进行基准测试,就很难确定。但是,作为一个数据点,如果您考虑以下单子操作:

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

然后当使用 -O2 使用 GHC 8.6.4 编译时,新类型——显然——被优化掉了,如果你将签名更改为:

foo :: ReaderT Int (State Double) Int

除了 foo 的两个参数被翻转。所以,根本没有性能差异,至少在这个简单的例子中是这样。

从风格上讲,您可能 运行 遇到这样一种情况:一种排序导致代码比另一种更好看,但通常它们之间没有太多选择。特别是,像上面那样的基本 monadic 动作在任何一种排序下看起来都完全相同。

无缘无故,我更喜欢#2,主要是因为 Env -> MyState -> (a, MyState) 对我来说看起来更自然。