猫效应和 IO 单子

Mau*_*aaf 5 monads functional-programming scala scala-cats

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

问题一:这个是参照透明的吗

问题 2:如何正确(基于单位/属性)测试这一点?不可能检查相等性,因为它将检查内存引用,并且不可能检查内部函数,因为如果我没有弄错的话,函数比较是不可能的。但是,我不想在单元测试中运行实际的副作用。另外,这是设计错误还是 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)
      )
}
Run Code Online (Sandbox Code Playgroud)

ada*_*ish 2

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

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

查看该代码,我注意到flatMapintryGetEnv可能足够复杂以保证测试。您可以通过将此逻辑提取到纯函数中来做到这一点。您可以(例如)重写它,以便有一个函数返回一个IO[String],然后编写一个(经过测试的)函数,将其转换为您想要的类型。

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

总之,我会考虑创建一个最小的函数来调用 System创建一个IO[A](在本例中为 an IO[Try[String]])。您可以选择通过模拟来测试这个最小的函数,但前提是您认为这样做可以增加价值。围绕这一点,您可以编写接受 an 的函数A,并通过将纯值传递给这些函数来测试它们。map请记住, on的签名IO如下,这里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
Run Code Online (Sandbox Code Playgroud)

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