用于包装不纯方法的效果?

Effects to use for wrapping impure methods?

我正在尝试了解如何使用效果单子(cats.effect.IOscalaz.IO 无关紧要)。假设我有以下方法:

def extract(str: String): String = {
    if(str.contains("123"))
        "123"
    else
        throw new IllegalArgumentException("Boom!")
}

因为这个方法是不纯的(抛出异常),我需要将它的结果与另一个有效的计算(网络-IO)结合起来,将它包装到 IO 中是一个好习惯,如下所示:

def extract(str: String): IO[String] = IO {
    if(str.contains("123"))
        "123"
    else
        throw new IllegalArgumentException("Boom!")
}

这是 Effect monad 的常见用例吗?

这是否合适,取决于你想表达什么。

这不像是您的第一个 def extract(str: String): String-方法定义在某种程度上是无效的:您只是将所有异常和副作用扫到地毯下,以便它们在签名中不可见。如果这与您当前的项目无关,并且如果一个程序只是崩溃并且抛出异常的长堆栈跟踪是可以接受的,那么就这样做,没问题(很容易想象一次性脚本会是这样的合适)。

如果改为声明 def extract(str: String): IO[String] = IO { ... },那么您至少可以在签名中看到函数 extract 可以做一些不纯的事情(在本例中抛出异常)。现在问题变成了:谁负责处理这个异常,或者你想在哪里处理这个异常?考虑一下:如果抛出异常,它将出现在您的代码中调用类似 yourProgram.unsafeRunSync() 的行中。在那里处理这个异常有意义吗?也许是,也许不是:没有人能告诉你。如果你只是想在你的 main 中捕获顶层的异常,记录它,然后 exit,那么它是合适的。

但是,如果您想立即处理异常,您有更好的选择。例如,如果您正在编写一个提示输入文件名的方法,然后尝试 extract 来自该名称的内容,最后对文件进行一些 IO,则 return 类型 IO[String] 可能太不透明了。您可能想使用其他一些 monad(OptionEitherTry 等)来表示失败的提取。例如,如果你使用 Option[String] 作为 return 类型,你不再需要在你的 main 方法中处理一个千里之外的模糊异常,而是你可以立即处理它,并且,例如,重复提示输入新文件名。

整个练习有点类似于提出一个如何处理一般异常的策略,只是在这里你在你的方法的类型签名中明确表达它。