haskell - 什么是 RWS Monad,何时使用

标签 haskell monad-transformers

我在 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 是否是正确的值。

现在考虑一个函数:
  • 需要从 ProgramConfig 数据类型中读取
  • 将值写入字符串以记录
  • 使用和修改 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 , StringProgramState用于输出。我们知道我们不能意外更改程序配置,或在计算中使用当前程序日志值。

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

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

    关于haskell - 什么是 RWS Monad,何时使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18909649/

    相关文章:

    haskell - 失败时如何保存信息?

    haskell - 推拉窗的一种

    Haskell - 类型类扩展

    f# - 将面向铁路的故障跟踪转换为 Rx 友好错误

    scala - 将\/[A, B] 提升到EitherT[Future, A, B]

    haskell - 如何理解 `MonadUnliftIO`对 "no stateful monads"的要求?

    haskell - 如何测试自定义 StateT 的 Monad 实例?

    text - Haskell、Char、Unicode 和土耳其语

    haskell - 用镜头构造谓词

    haskell - 如何使用递归方案更新结构?