【问题标题】:Why should one prefer Option for error handling over exceptions in Scala?为什么人们更喜欢使用 Option 来处理 Scala 中的异常而不是异常?
【发布时间】:2014-07-28 12:13:15
【问题描述】:

所以我正在学习函数式 Scala,书上说异常会破坏引用透明度,因此应该使用 Option,如下所示:

def pattern(s: String): Option[Pattern] = {
  try {
    Some(Pattern.compile(s))
  } catch {
    case e: PatternSyntaxException => None
  }
}

这看起来很糟糕;我的意思是它似乎等同于:

catch(Exception e){
    return null;
}

除了我们可以区分“null for error”和“null as真值”这一事实。似乎它至少应该返回包含错误信息的内容,例如:

catch {
    case e: Exception => Fail(e)
}

我错过了什么?

【问题讨论】:

  • 哪本书?我看不出异常会如何破坏参照透明度。在 Scala 中,Nothing 是一种类型(尽管很特殊)。如果您的函数在给定的常量参数上始终抛出异常,那在我的理解中是透明的。
  • @0__: 这本书是manning.com/bjarnason "throw and catch 意味着如果我们用 throw new 替换 x + y 中的 x Exception("fail!") + y,我们的程序有不同的结果"
  • 嗯,我认为他来自讨厌分类型的学校。在 Scala 中,Nothing 是底部类型,所以我不会称它为“不同的结果”。无论如何,如果您想直接在调用端处理错误,那么使用 TryEither 等不同的机制可能会更有用。
  • 对于异常抛出的怪异控制流,有几种代数替代方案,Option 只是您对错误详细信息不感兴趣时​​使用的替代方案。这本书可能首先介绍了这个,因为它是最简单的。

标签: scala exception-handling functional-programming


【解决方案1】:

在这个特定部分,Option 主要用作示例,因为使用的操作(计算 mean)是一个偏函数,它不会为所有可能的值生成一个值(集合可能为空,因此无法计算平均值),Option 在这里可能是一个有效的案例。如果由于集合为空而无法计算 mean,则返回 None

但是还有很多其他方法可以解决这个问题,你可以使用Either[L,R]Left 是错误结果,Right 是好的结果,你仍然可以抛出异常并包装它在Try 对象内(由于在PromiseFuture 计算中使用,现在似乎更常见),如果错误实际上是验证问题,您可以使用ScalaZ Validation

您应该从这部分采取的主要概念是错误应该是函数返回类型的一部分,而不是某些类型无法合理声明的魔术操作(异常)。

作为一个无耻的插件,我写了关于 Either and Try here 的博客。

【讨论】:

  • 本书第4.4节讲了Either,练习提示Validation(本书后面也会提到)
【解决方案2】:

如果您不问“为什么Option 比例外更好?”,回答这个问题会更容易。和“为什么Option 比 null 更好?”和“为什么OptionTry 更好?”都在同一时间。

第一个问题的答案是,在并非真正异常的情况下使用异常会混淆程序的控制流。这就是引用透明性的用武之地——如果我可以从值的角度思考并且不必跟踪异常被抛出和捕获的位置,那么我(或你)就更容易推理你的代码。

第二个问题(为什么不为 null?)的答案类似于“您曾经在 Java 中处理过NullPointerException 吗?”。

对于第三个问题,一般来说您是对的——最好使用Either[Throwable, A]Try[A] 之类的类型来表示可能失败的计算,因为它们允许您传递有关失败的更详细信息。但是,在某些情况下,当一个函数只能以一种明显的方式失败时,使用Option 是有意义的。例如,如果我在地图中执行查找,我可能并不真正需要或不想要像 Either[NoSuchElementException, A] 这样的错误,其中错误是如此抽象,以至于我可能最终将其包装在更领域的东西中 -反正具体。所以地图上的get 只返回一个Option[A]

【讨论】:

  • “在并非真正异常的情况下使用异常会混淆程序的控制流”:非常好的一点,但经常被异常爱好者忽略。 +1
  • +1 用于明确区分三个隐含的问题。也许我会补充:在考虑更大的图景时,留在惯用的框架内也是有道理的。与普通异常相比,Option 是一个 monad。这为您提供了单子表达式的全部功能,即定义为一系列步骤的计算。最后,这允许使用像 flatMap 和 Scala 的 for 这样的一元语法概念,这总体上会产生更同质的语法。
【解决方案3】:

你应该使用util.Try:

scala> import java.util.regex.Pattern
import java.util.regex.Pattern

scala> def pattern(s: String): util.Try[Pattern] = util.Try(Pattern.compile(s))
pattern: (s: String)scala.util.Try[java.util.regex.Pattern]


scala> pattern("<?++")
res0: scala.util.Try[java.util.regex.Pattern] =
Failure(java.util.regex.PatternSyntaxException: Dangling meta character '+' near index 3
<?++
   ^)

scala> pattern("[.*]")
res1: scala.util.Try[java.util.regex.Pattern] = Success([.*])

【讨论】:

    【解决方案4】:

    天真的例子

    def pattern(s: String): Pattern = {
      Pattern.compile(s)
    }
    

    有一个副作用,它可以通过结果以外的其他方式影响使用它的程序(它可能导致异常)。这在函数式编程中是不鼓励的,因为它会增加代码的复杂性。
    代码

    def pattern(s: String): Option[Pattern] = {
      try {
        Some(Pattern.compile(s))
      } catch {
        case e: PatternSyntaxException => None
      }
    }
    

    封装了程序的产生副作用的部分。 Pattern 失败的信息丢失了,但有时只关心它是否失败。如果方法失败的原因很重要,可以使用 Try(http://www.scala-lang.org/files/archive/nightly/docs/library/index.html#scala.util.Try):

    def pattern(s: String): Try[Pattern] = {
       Try(Pattern.compile(s))
    }
    

    【讨论】:

      【解决方案5】:

      我认为其他两个答案为您提供了有关如何进行的很好的建议。我仍然认为抛出异常在 Scala 的类型系统中得到了很好的体现,使用底部类型Nothing。所以它的类型很好,我不会准确地称之为“魔术操作”。

      但是...如果您的方法通常会导致无效值,也就是说,如果您的调用方非常合理地希望立即处理此类无效值,则使用OptionEitherTry是一个很好的方法。在您的呼叫站点并不真正知道如何处理此类无效值的情况下,特别是如果它是 异常 条件而不是常见情况,那么您应该使用异常 IMO。

      异常的问题并不在于它们不能很好地使用函数式编程,而是当你有副作用时它们很难推理。因为那时您的呼叫站点必须确保在发生异常时撤消副作用。如果您的调用站点纯粹是功能性的,则传递异常不会造成任何损害。

      如果任何对整数执行任何操作的函数由于被零除或溢出的可能性而将其返回类型声明为Try,这可能会使您的代码完全混乱。使用异常的另一个很好的理由是无效的参数范围或要求。如果您希望参数是0x 之间的整数,那么如果它不满足该属性,您可能会抛出IllegalArgumentException;在 Scala 中很方便:require(a &gt;= 0 &amp;&amp; a &lt; x)

      【讨论】:

      • “异常在 Scala 的类型系统中得到了很好的体现”:我不同意这里。您似乎只考虑方法 always 抛出的情况(例如sys.error),但大多数方法只会在例外情况下抛出异常,在这种情况下返回类型不能是Nothing。因此,方法可能抛出的事实完全丢失在签名中,也就是(黑色)“魔法正在发生”。这与使用 TryEither 形成对比,这是明确的。基本上,该论点是对Option 比使用nulls 更好的原因的概括。
      猜你喜欢
      • 2012-04-08
      • 2010-09-11
      • 1970-01-01
      • 2013-09-02
      • 2011-07-29
      • 2010-09-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多