在设计编程模型时,我总是遇到一个难题:哪种方法更好:
type MyMonad1 = StateT MyState (Reader Env)
type MyMonad2 = ReaderT Env (State MyState)
使用一个单子与另一个单子有什么好处和取舍?有关系吗?性能如何?
答案 0 :(得分:6)
在一般情况下,monad转换器的不同顺序将导致不同的行为,但是正如注释中所指出的,对于“状态”和“阅读器”这两个顺序,我们具有以下同构直到新类型:
StateT MyState (Reader Env) a ~ MyState -> Env -> (a, MyState)
ReaderT Env (State MyState) a ~ Env -> MyState -> (a, MyState)
所以唯一的区别是参数顺序之一,否则这两个单子在语义上是等效的。
关于性能,如果不对实际代码进行基准测试,就很难确定。但是,作为一个数据点,如果考虑以下单子动作:
foo :: StateT Double (Reader Int) Int
foo = do
n <- ask
modify (* fromIntegral n)
gets floor
然后,当使用-O2
在GHC 8.6.4中进行编译时,显然会优化掉了新类型,如果将签名更改为以下内容,则会生成完全相同的Core:
foo :: ReaderT Int (State Double) Int
除了foo
的两个参数被翻转。因此,至少在这个简单的示例中,根本没有性能差异。
从风格上讲,您可能会遇到一种情况,导致一种顺序比另一种顺序的代码看起来更好,但是通常在它们之间没有太多选择。尤其是上面的一种基本单调操作在任何一种排序下都看起来完全一样。
出于没有充分的理由,我倾向于#2,主要是因为Env -> MyState -> (a, MyState)
对我来说看起来更自然。