我一直在尝试掌握IO monad一段时间,这很有意义。如果我没记错的话,目标是将副作用的描述与实际执行分开。如下例所示,Scala有一种方法来获取不是参照透明的环境变量。提出了两个问题。
问题1:此参照透明吗
问题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)
)
}
最佳答案
最好使用IO
来包装程序中来自不纯来源的值,就像示例中的System调用一样。这将给您返回IO[A]
,其内容为“我能够通过不正当手段获得A
”。之后,您可以使用通过A
,map
等作用于该flatMap
的纯/参照透明函数。
这导致两个答案。我想问一下,您要测试什么性质?
查看该代码,我注意到flatMap
中的tryGetEnv
可能很复杂,需要进行测试。您可以通过将此逻辑提取到纯函数中来实现。您可能(例如)重写它,以便有一个函数返回一个IO[String]
,然后编写一个(经过测试的)函数,将其转换为所需的类型。
IO完全按照您说的去做,但这显然不包括使代码参照透明!如果要在此处测试实际的副作用,则可以考虑将System作为参数传递并对其进行模拟,以进行测试,就像在不使用IO
的程序中一样。
因此,总而言之,我将考虑创建一个最小函数,该函数对System
进行调用以创建IO[A]
(在本例中为IO[Try[String]]
)。您可能选择通过模拟测试此最小功能,但前提是您认为自己正在通过这样做来增加价值。围绕此,您可以编写采用A
的函数,并通过将纯值传递给这些函数来对其进行测试。请记住,map
上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
极端地讲,这种模式鼓励您仅在程序的边缘(例如
IO
函数)创建main
值。然后,可以从纯函数创建程序的其余部分,这些纯函数将在程序运行时根据IO类型的值起作用。