当我设计我的编程模型时,我总是陷入困境,哪种方法更好:
type MyMonad1 = StateT MyState (Reader Env)
type MyMonad2 = ReaderT Env (State MyState)
使用一个monad而不是另一个monad有什么好处和权衡?这有关系吗?性能怎么样?
当我设计我的编程模型时,我总是陷入困境,哪种方法更好:
type MyMonad1 = StateT MyState (Reader Env)
type MyMonad2 = ReaderT Env (State MyState)
使用一个monad而不是另一个monad有什么好处和权衡?这有关系吗?性能怎么样?
在一般情况下,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
然后当使用 GHC 8.6.4 编译时-O2
,新类型 - 显然 - 优化了,如果您将签名更改为,这将生成完全相同的核心:
foo :: ReaderT Int (State Double) Int
除了要foo
翻转的两个参数。因此,至少在这个简单的示例中,根本没有性能差异。
从风格上讲,您可能会遇到一种排序导致代码比另一种更好看的情况,但通常它们之间不会有太多选择。特别是,像上面这样的基本一元动作在任何一种排序下看起来都完全相同。
没有充分的理由,我倾向于支持#2,主要是因为Env -> MyState -> (a, MyState)
看起来对我来说更自然。