29

我在 mtl 库中查找内容时遇到了 RWS Monad 及其 MonadTransformer。那里没有真正的文档,我想知道这是什么以及它在哪里使用。

我发现 RWS 是 Reader、Writer、State 的首字母缩写词,那是这三个 monad 转换器的堆栈。我无法弄清楚为什么这比 State 本身更好。

4

2 回答 2

36

最实际的原因是为了可测试性和更精确的类型签名。

haskell 的关键优势在于您可以如何通过类型系统指定函数的功能。比较 c#/java 类型:

public int CSharpFunction(int param) { ...

有一个haskell:

someFunction :: Int -> Int

haskell 不仅告诉我们参数所需的类型和返回类型,还告诉我们函数可能会影响什么。例如,它不能做任何 IO,也不能读取或更改任何全局状态,或访问任何静态配置数据。对于 c# 函数,两者都不是真的,但我们不能说。

这对测试有很大帮助。我们知道,我们需要用 haskell 测试的唯一事情someFunction是它是否获得了某些样本输入的预期输出。不需要任何可能的设置,并且该函数永远不会为相同的输入给出不同的结果。这使得使用纯函数进行测试变得非常简单。


然而,一个函数通常不能是纯的。例如,它可能需要访问一些全局信息只是为了阅读。我们可以在函数中添加另一个参数:

readerFunc :: GlobalConfig -> Int -> Int

但是使用 monad 通常更容易,因为它们更容易组合:

readerFunc2 :: Int -> Reader GlobalConfig Int

测试它几乎和测试纯函数一样容易。我们只需要测试输入 Int 值和 GlobalConfig 读取器配置值的各种排列。

一个函数可能需要写出值(例如用于记录)。这也可以用 monad 来完成:

writerFunc :: Int -> Writer String Int

再次测试这几乎与纯函数一样容易。我们只是测试对于给定的Int输入,Int是否返回适当的,以及正确的最终作者String

另一个函数可能需要读取和更改状态:

stateFunc :: Int -> State GlobalState Int

不过,这更难测试。我们不仅要使用各种输入 Int 和初始 GlobalStates 检查输出,还需要测试最终 GlobalState 是否是正确的值。


现在考虑一个函数:

  • 需要从 ProgramConfig 数据类型中读取
  • 将值写入字符串以进行日志记录
  • 使用和修改一个ProgramState值。

我们可以这样做:

data ProgramData = ProgramData { pState :: ProgramState, pConfig :: ProgramConfig, pLogs :: String }
complexFunction :: Int -> State ProgramData Int

但是,这种类型不是很准确。这意味着 ProgramConfig 可能会更改,但不会更改。这也意味着该函数可以使用 pLogs 值,但它不会。此外,测试它更复杂,因为理论上,该函数可能会意外更改程序配置,或在其计算中使用当前 pLogs 值。

这可以大大改善:

betterFunction :: Int -> RWS ProgramConfig String ProgramState Int

这种类型非常准确。我们知道 ProgramConfig 只会被读取。我们知道String唯一的改变。ProgramState正如预期的那样,唯一需要读写的部分是。这更容易测试,因为我们只需要测试 ProgramConfig、ProgramState 和 for input 的不同组合,并Int检查 outputInt和for output。我们知道我们不能意外更改程序配置,或在计算中使用当前程序日志值。StringProgramState


haskell 类型系统可以帮助我们,我们应该给它尽可能多的信息,这样它就可以在我们做之前捕获错误。

(请注意,此答案中的每个 haskell 类型都可能等同于顶部的 c# 类型,具体取决于 c# 函数的实际实现方式)。

于 2013-09-20T07:36:22.763 回答
3

状态是一个非常笼统的概念,因此可以用于许多事情。Reader 和 Writer 可以被认为是具有某些约束的特殊形式的 State,您只能从 reader 读取,而只能向 writer 写入。使用这些特殊的状态形式,您可以更明确地了解您要达到的目标或确切的意图。

另一个类比可能是使用 map/dictionary 对任何东西(对象、数据、事件处理程序等)进行建模,但使用更专业的 map/dictionary 形式会使事情更加明确

于 2013-09-20T06:51:49.817 回答