我在mtl库中查找某些内容时遇到了RWS Monad及其MonadTransformer。那里没有真正的文档,我想知道这是什么以及它在哪里使用。
我已经发现RWS是Reader,Writer,State的缩写,那就是这三个monad变换器的堆栈。我无法弄清楚为什么这比国家本身更好。
答案 0 :(得分:30)
最实际的原因是可测试性和更精确的类型签名。
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
虽然这很难测试。我们不仅需要使用各种输入Ints和初始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和Int
的不同组合以进行输入,并检查输出Int
,String
和ProgramState
的输出。我们知道我们不能无意中更改程序配置,或在计算中使用当前程序日志值。
haskell类型系统可以帮助我们,我们应该尽可能多地提供信息,以便它可以在我们做之前发现错误。
(请注意,此答案中的每个haskell类型可能等同于顶部的c#类型,具体取决于c#函数的实际实现方式。)
答案 1 :(得分:3)
国家是一个非常概括的概念,因此可用于许多事情。读者和作者可以被认为是一种具有某些约束的特殊形式的国家,你只能从读者那里读取而你只能写作者。使用这些特殊形式的状态,您可以更明确地明确关于您要实现的目标或意图究竟是什么。
另一个类比可能是使用地图/字典来建模任何东西(对象,数据,事件处理程序等),但使用更专业的地图/字典形式可以使事情更加明确