我在 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
值(value)。 我们可以这样做:
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 和 Int
的不同组合即可。输入,并检查输出 Int
, String
和 ProgramState
用于输出。我们知道我们不能意外更改程序配置,或在计算中使用当前程序日志值。haskell 类型系统可以帮助我们,我们应该给它尽可能多的信息,这样它就可以在我们做之前捕获错误。
(请注意,此答案中的每个 haskell 类型都可能等同于顶部的 c# 类型,具体取决于 c# 函数的实际实现方式)。
关于haskell - 什么是 RWS Monad,何时使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18909649/