什么是RWS Monad以及何时使用

Joh*_*ler 27 haskell monad-transformers

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

我发现RWS是Reader,Writer,State的首字母缩略词,这就是这三个monad变换器的堆栈.我无法弄清楚为什么这比国家本身更好.

Dav*_*ani 30

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

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

public int CSharpFunction(int param) { ...
Run Code Online (Sandbox Code Playgroud)

有一个haskell之一:

someFunction :: Int -> Int
Run Code Online (Sandbox Code Playgroud)

haskell不仅告诉我们参数和返回类型所需的类型,还告诉我们函数可能会影响的类型.例如,它不能执行任何IO,也不能读取或更改任何全局状态,也不能访问任何静态配置数据.c#函数可能都不是,但我们无法分辨.

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


但是,通常功能不能是纯粹的.例如,它可能需要访问一些仅供阅读的全局信息.我们可以在函数中添加另一个参数:

readerFunc :: GlobalConfig -> Int -> Int
Run Code Online (Sandbox Code Playgroud)

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

readerFunc2 :: Int -> Reader GlobalConfig Int
Run Code Online (Sandbox Code Playgroud)

测试这几乎就像测试纯函数一样简单.我们只需要测试输入Int值和GlobalConfig reader配置值的各种排列.

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

writerFunc :: Int -> Writer String Int
Run Code Online (Sandbox Code Playgroud)

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

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

stateFunc :: Int -> State GlobalState Int
Run Code Online (Sandbox Code Playgroud)

但这很难测试.我们不仅需要使用各种输入Ints和初始GlobalStates来检查输出,而且我们还需要测试最终的GlobalState是否是正确的值.


现在考虑一个函数:

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

我们可以这样做:

data ProgramData = ProgramData { pState :: ProgramState, pConfig :: ProgramConfig, pLogs :: String }
complexFunction :: Int -> State ProgramData Int
Run Code Online (Sandbox Code Playgroud)

但是,这种类型不是很准确.这意味着ProgramConfig可能会被更改,但它不会.它还暗示该函数可能使用pLogs值,而不是.此外,测试它更复杂,理论上,该函数可能会意外地更改程序配置,或在其计算中使用当前的pLogs值.

这可以大大改善:

betterFunction :: Int -> RWS ProgramConfig String ProgramState Int
Run Code Online (Sandbox Code Playgroud)

这种类型非常准确.我们知道ProgramConfig只能从中读取.我们知道这String只会改变.ProgramState正如预期的那样,唯一需要阅读和写作的部分.这是更容易测试,因为我们只需要测试ProgramConfig,ProgramState和不同的组合Int输入,并检查输出Int,String并且ProgramState输出.我们知道我们不能无意中更改程序配置,或在我们的计算中使用当前程序日志值.


haskell类型系统可以帮助我们,我们应该尽可能多地提供它,以便它可以在我们做之前发现错误.

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

  • @Vektorweg:这实际上相当于`Int - > RWS Config Log State Int`,对于一个简单的程序来说非常好.但是,monadic版本最终更容易用于更复杂的程序,因为您不必担心将正确的状态值传递给所有相关函数,也不必在最后建立和返回正确的日志值. (3认同)
  • 可组合性和灵活性。通过使用“RWS”,您可以访问具有“MonadReader”、“MonadWriter”和“MonadState”类型的内容,这些内容可能是由不知道您的应用程序使用什么类型的其他人编写的。此外,在定义 `RWS` 类型的值时,您不需要提供参数,其中一些甚至可能不会使用(因为并非每个函数都使用配置、状态和日志)。如果您以后想要更多/不同的功能,则需要修改所有定义。将 `RWS` 更改为更强大的 monad 不需要任何此类更改...... (2认同)