猫效应和 IO monad

Cats-effect and the IO monad

一段时间以来,我一直在尝试掌握 IO monad,这很有意义。如果我没记错的话,目标是将副作用的描述和实际执行分开。如下例所示,Scala 有一种获取非引用透明环境变量的方法。出现了两个问题。

问题一:这个是引用透明的吗

问题 2:如何正确 (unit/property-based) 测试这个?不可能检查相等性,因为它会检查内存引用,并且不可能检查内部函数,因为如果我没记错的话,函数比较是不可能的。但是,我不想 运行 单元测试中的实际副作用。另外,这是设计错误还是对 IO monad 的误用?

case class EnvironmentVariableNotFoundException(message: String) extends Exception(message)

object Env {
  def get(envKey: String): IO[Try[String]] = IO.unit.flatMap((_) => IO.pure(tryGetEnv(envKey)))

  private[this] def tryGetEnv(envKey: String): Try[String] =
    Try(System.getenv(envKey))
      .flatMap(
        (x) =>
          if (x == null) Failure(EnvironmentVariableNotFoundException(s"$envKey environment variable does not exist"))
          else Success(x)
      )
}

最好使用 IO 来包装程序中来自不纯来源的值,就像示例中的系统调用一样。这将返回一个 IO[A],其内容为“我能够通过不纯的方式获得一个 A”。此后,您可以使用通过 mapflatMap 等作用于 A 的纯/引用透明函数

这导致两个答案。我想问一下,您要测试的 属性 是什么?

查看该代码,我注意到 tryGetEnv 中的 flatMap 可能很复杂,需要进行测试。您可以通过将此逻辑提取到一个纯函数中来做到这一点。您可能(例如)重写它,所以有一个 returns 和 IO[String] 的函数,然后编写一个(测试的)函数将其转换为您想要的类型。

IO 完全按照你说的做,但这显然不包括使代码引用透明!如果您想在这里测试实际的副作用,您可以考虑将 System 作为参数传递并模拟它进行测试,就像在不使用 IO.

的程序中一样

所以总而言之,我会考虑创建一个 minimal 函数来调用 System 来创建一个 IO[A](在这种情况下,一个 IO[Try[String]])。您可以选择通过模拟来测试这个最小功能,但前提是您觉得这样做会增加价值。围绕这一点,您可以编写接受 A 的函数,并通过将纯值传递给这些函数来测试这些函数。请记住,IOmap 的签名如下,这里的 f 是一个纯(可测试)函数!

sealed abstract class IO[+A] {

  def map[B](f: A => B): IO[B]
             ^ f is a pure function!
               test it by passing A values and verifying the Bs

在极端情况下,此模式鼓励您仅在程序的最边缘创建 IO 值(例如,您的 main 函数)。然后可以从纯函数创建程序的其余部分,这些纯函数作用于程序运行时来自 IO 类型的值。