当我设计编程模型时,我总是左右为难,哪种方法更好:
type MyMonad1 = StateT MyState (Reader Env) type MyMonad2 = ReaderT Env (State MyState)
使用一个单子栈和使用另一个单子栈之间有什么好处和权衡?这有关系吗?性能呢?
yshpjwxd1#
一般情况下,单子变换器的不同排序会导致不同的行为,但正如评论中所指出的,对于“state”和“reader”这两种排序,我们有以下同构直到newtype:
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,如果你把签名改为:
-O2
foo :: ReaderT Int (State Double) Int
除了foo的两个参数被翻转了,所以,至少在这个简单的例子中,没有任何性能上的差异。从风格上来说,你可能会遇到这样的情况:一种排序方式比另一种排序方式产生的代码看起来更漂亮,但通常在它们之间没有太多可供选择的。特别是,像上面这样的基本一元动作在两种排序方式下看起来完全一样。没有什么好的理由,我倾向于选择#2,主要是因为Env -> MyState -> (a, MyState)在我看来更自然。
foo
Env -> MyState -> (a, MyState)
1条答案
按热度按时间yshpjwxd1#
一般情况下,单子变换器的不同排序会导致不同的行为,但正如评论中所指出的,对于“state”和“reader”这两种排序,我们有以下同构直到newtype:
因此,唯一的区别是参数顺序不同,这两个单子在其他方面是等价的。
至于性能,如果不对实际代码进行基准测试,就很难确定。但是,作为一个数据点,如果考虑下面的一元操作:
那么当使用
-O2
编译GHC 8.6.4时,新的类型--显然--被优化掉了,这会生成完全相同的Core,如果你把签名改为:除了
foo
的两个参数被翻转了,所以,至少在这个简单的例子中,没有任何性能上的差异。从风格上来说,你可能会遇到这样的情况:一种排序方式比另一种排序方式产生的代码看起来更漂亮,但通常在它们之间没有太多可供选择的。特别是,像上面这样的基本一元动作在两种排序方式下看起来完全一样。
没有什么好的理由,我倾向于选择#2,主要是因为
Env -> MyState -> (a, MyState)
在我看来更自然。