我在 mtl 库中查找内容时遇到了 RWS Monad 及其 MonadTransformer。那里没有真正的文档,我想知道这是什么以及它在哪里使用。
我发现 RWS 是 Reader、Writer、State 的首字母缩写词,那是这三个 monad 转换器的堆栈。我无法弄清楚为什么这比 State 本身更好。
我在 mtl 库中查找内容时遇到了 RWS Monad 及其 MonadTransformer。那里没有真正的文档,我想知道这是什么以及它在哪里使用。
我发现 RWS 是 Reader、Writer、State 的首字母缩写词,那是这三个 monad 转换器的堆栈。我无法弄清楚为什么这比 State 本身更好。
最实际的原因是为了可测试性和更精确的类型签名。
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 是否是正确的值。
现在考虑一个函数:
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。我们知道我们不能意外更改程序配置,或在计算中使用当前程序日志值。String
ProgramState
haskell 类型系统可以帮助我们,我们应该给它尽可能多的信息,这样它就可以在我们做之前捕获错误。
(请注意,此答案中的每个 haskell 类型都可能等同于顶部的 c# 类型,具体取决于 c# 函数的实际实现方式)。
状态是一个非常笼统的概念,因此可以用于许多事情。Reader 和 Writer 可以被认为是具有某些约束的特殊形式的 State,您只能从 reader 读取,而只能向 writer 写入。使用这些特殊的状态形式,您可以更明确地了解您要达到的目标或确切的意图。
另一个类比可能是使用 map/dictionary 对任何东西(对象、数据、事件处理程序等)进行建模,但使用更专业的 map/dictionary 形式会使事情更加明确