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)
对我来说看起来更自然。
当我设计我的编程模型时,我总是为哪种方法更好而进退两难:
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)
对我来说看起来更自然。