StateT优于Reader的状态和ReaderT超过State的状态之间有显着差异吗?

rad*_*row 4 monads performance haskell state-monad reader-monad

在设计编程模型时,我总是会遇到哪种方法更好的难题:

type MyMonad1 = StateT MyState (Reader Env)
type MyMonad2 = ReaderT Env (State MyState)
Run Code Online (Sandbox Code Playgroud)

在使用一个单子与另一个单子之间有什么好处和取舍?有关系吗?性能如何?

K. *_*uhr 6

在一般情况下,monad转换器的不同顺序将导致不同的行为,但是正如注释中所指出的,对于“状态”和“阅读器”这两个顺序,我们具有以下同构直到新类型:

StateT MyState (Reader Env) a  ~  MyState -> Env -> (a, MyState)
ReaderT Env (State MyState) a  ~  Env -> MyState -> (a, MyState)
Run Code Online (Sandbox Code Playgroud)

因此,唯一的区别是参数顺序之一,否则这两个单子在语义上是等效的。

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

foo :: StateT Double (Reader Int) Int
foo = do
  n <- ask
  modify (* fromIntegral n)
  gets floor
Run Code Online (Sandbox Code Playgroud)

然后当使用GHC 8.6.4编译-O2时,newtypes是-很明显-优化掉了,这产生准确,如果你更改签名相同的核心:

foo :: ReaderT Int (State Double) Int
Run Code Online (Sandbox Code Playgroud)

除了foo要翻转的两个参数。因此,至少在这个简单的示例中,根本没有性能差异。

从风格上讲,您可能会遇到一种情况,导致一种顺序比另一种顺序的外观更好看,但是在它们之间通常没有太多选择。特别是,上述任一基本基本动作在任何一种排序下都看起来完全相同。

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

  • 一个小小的争论:当我们谈论擦除新类型时,仅仅拥有两个同构类型还不够。还必须检查相关实例是否遵守同构。(例如,考虑“全部”和一个假设的“异或”,它们都将解散为“布尔”,但其“ Monoid”实例不尊重“ id”或“ not”同构)。但是,在这种情况下,因为实例确实确实适当地对应。 (2认同)