【问题标题】:Get Option value or throw an exception获取 Option 值或抛出异常
【发布时间】:2023-03-11 20:19:01
【问题描述】:

给定一个选项,获取其值或尝试抛出异常的惯用方法是什么?

def foo() : String = {
  val x : Option[String] = ...
  x.getOrException()
}

【问题讨论】:

  • 为什么不.get
  • @MyseriousDan 我刚刚偶然发现了这个案例。我有一种情况,我绝对 100% 确定它必须返回 Some,如果不是,这是一个严重的问题,必须抛出。但我想抛出我自己的异常和任何模棱两可的 NoSuchElementException 异常。此外,大多数情况下,此方法可以安全地返回 None。
  • 真正的问题是为什么你的类型说它可能会丢失,而你知道它不是。如果你能用类型表达你的知识,那通常是最好的。它可能需要稍微改造你的课程,但从长远来看通常会更愉快。
  • @TravisBrown 的答案应该是公认的,而不是我的。我的不是惯用的,我还是“新手”时写的(我仍然是,但级别 2)。举个例子做什么... ;-)
  • 如果你不得不抛出异常,那你就做错了。使用 Option 的惯用方式是返回 None 并且根本不抛出异常。

标签: scala


【解决方案1】:

throw“语句”实际上是 Scala 中的一个表达式,它的类型为 Nothing,它是所有其他类型的子类型。这意味着您可以使用普通的旧 getOrElse:

def myGet[A](oa: Option[A]) = oa.getOrElse(throw new RuntimeException("Can't."))

不过,你真的,真的不应该这样做。

【讨论】:

  • 为什么不呢?这样做的更优雅/惯用的方式是什么?模式匹配?
  • 如果您在整个程序中将故障建模为值(并为真正的异常问题保存异常,例如OutOfMemoryError),您可以从类型系统中获得更多收益。
  • 我不认为建模失败的优势是任何不可恢复问题的价值(其中 OOME 显然是其中之一)。以从配置文件中读取元数据为例。程序的正确执行需要格式良好的配置文件。如果由于用户错误导致配置文件包含无效条目,则程序无法继续。因此,在这种情况下展开堆栈并退出似乎是合适的。
  • @JonathanNeufeld Travis Brown 的评论听起来更像是一种更纯粹的 fp 方法。它不适合每个人/每个用例。我更支持你——只是保释,不要驱动太多的逻辑/花哨类型的系统处理。
  • 有很多情况,e.g.在 Akka 中,投掷是完全正常的。它没有任何问题。
【解决方案2】:

(编辑:这不是最好或最惯用的方法。我在不熟悉 Scala 时写了它。我把它留在这里作为 not 的示例. 现在我会做@TravisBrown)

我认为这真的归结为两件事:

  • 您如何确定价值存在?
  • 如果不是,您想如何反应

如果在您的代码中,您期望该值在那里,并且在远程情况下,您不希望您的程序快速失败,那么我只会做一个普通的get,如果没有价值,让Scala抛出一个NoSuchElementException

def foo() : 字符串 = {
  val x : 选项 [字符串] = ...
  x.get
}

如果您想以不同的方式处理案例(抛出您自己的异常),我认为更优雅的方式如下所示:

def foo(): 字符串 = {
  val x: 选项 [字符串] = 无
  x匹配{
    案例一些(价值)=>价值
    case None => throw new MyRuntimeException("blah")
  }
}

当然,如果您想为OptionNone 的情况提供自己的替代值,您只需使用getOrElse

def foo(): 字符串 = {
  val x: 选项 [字符串] = 无
  x.getOrElse("我的替代值")
}

【讨论】:

  • 当然应该注意,匹配 Some(value) 只是为了提取相同的值不是惯用的 Scala,如果这就是人们想要做的一切,应该首选getOrElse。我只是想说明“如果定义,则评估为X,如果没有,则抛出异常”的情况。
  • quote Guillaume Belrose:“我想我现在还处于业余时间:-)”
  • 您还可以通过在getOrElse 中抛出异常来结合@SebastianN. 的建议,如下所示:x.getOrElse(throw new MyRuntimeException("message"))
  • @TravisBrown:在获得更多 Scala 经验之后,我不得不说你是对的。像这样匹配 Option 而不是使用 getOrElse 是不必要的,而且不是惯用的。你的应该是公认的答案。
【解决方案3】:

我希望这将帮助您了解如何使用类型来表示错误(以及一般的影响)。

函数式 Scala 中的错误处理策略

  • 使用Option 返回可选值。 例如 - 无法在存储中找到实​​体。

  • 使用Option(possiblyNull) 来避免Some(null) 的实例。

  • 使用Either[Error, T] 报告预期的失败。 例如 - 电子邮件格式错误,无法将字符串解析为数字等。

  • 将您的错误建模为ADTs(简单地说是一种类型层次结构)以使用它,例如,在 Either 的左侧来表示更复杂的错误场景。

  • 抛出 Exception 仅表示意外且不可恢复的故障。就像缺少配置文件一样。

  • 使用Either.catchOnlyTryCats.IO(高级)而不是catch 块来处理意外故障。提示:你仍然可以使用 ADT,但 extend them 来自 throwables。更多关于Either vs Try

  • 使用 Cats lib 中的 Validated 数据类型来累积错误而不是快速失败 (Either),但更喜欢模块级别的 Either 以简化程序的组合(以具有相同的类型)。 例如 - 表单数据验证,解析错误累积。

  • 使用提到的类型并且不要抢先优化程序 - 因为很可能瓶颈出现在业务逻辑中,而不是在效果类型中。

这种方法将简化代码的维护和更新,因为您无需了解实现细节(也称为本地推理)即可对其进行推理。此外 - 减少错误 - 您不能错过类型中的错误。并且更容易编写程序(在mapflatMap 和其他组合器的帮助下)——因为它在类型级别上更简单,而不是非本地异常和副作用。
More 关于学习函数式 Scala .


但请注意,有时使用这种方法,类型可能会堆积起来,并且可能会变得更难组合。给定,例如:x: Future[Either[Error, Option[T]]] 你可以做什么:

  • mapflatMap 与模式匹配结合使用以组成此类类型的不同值,例如:
x.faltMap { case Right(Some(v)) => anotherFuture(v); case Left(er) => ... }
  • 如果没有帮助,您可以尝试使用 MonadTransformers(不要害怕名称,它只是对 Either 和 Future 等效果类型的封装)
  • 另外,一个选项是通过 Throwable 中的 extending them 简化您的错误 ADT,以将其与 Future 统一,然后它将是 Future[Option[T]]


最后,在您的情况下,一种选择是:

def foo() : Either[Error, String] = {
    val x : Option[String] = ...
    x match {
        case Some(v) => Right(v)
        case None => Left(Error(reason))
    }
}

【讨论】:

  • 相当不错的纲要。你在句子中间留下了最后一个“想法”;)
  • @YordanGeorgiev 链接已失效,已删除
  • 这没有回答这个问题:“获取其值或抛出异常尝试的惯用方式是什么?”我想也许您认为 OP 被误导了,并且会从一些教育中受益;也许你是对的。但是有时(例如由于无效的用户输入)如果一个选项是无的,并且这个 SO 帖子正在询问如何做到这一点,那么人们想要抛出一个异常。据我所知,您的回答向 OP 展示了如何建模事物,但没有向他们展示在没有时抛出的惯用方式(带有自定义错误消息)。
【解决方案4】:

只需使用 .get 方法。

def get[T](o:Option[T]) = o.get

如果 o 是 None 的实例,它将抛出 NoSuchElementException。

基本上,我会使用这样的选项:

def addPrint(oi:Option[Int]) = oi.map(_+1).foreach(println)
addPrint(Some(41))
addPrint(Some(1336))
addPrint(None)

避免您的具体问题。

【讨论】:

  • 我假设 OP 想要更多地控制抛出的异常,但即使不是,get 也是一个坏主意——它只是掩盖了你正在做一些不愉快的事情这一事实。当您认为这不仅仅是一次性代码并希望使其更安全时,使用getOrElse 并显式抛出异常就更难错过了。
  • 当然,在我看来,基本上你应该永远使用任何类型的 get on 选项,除非你真的必须这样做。我会使用 foreach 或 map 来做带有选项的计算。
【解决方案5】:

Scala 现在支持使用 getOrElse() 方法在地图上执行此操作,请参阅 documentation here

正如已经指出的,在 Scala 中抛出异常也是一个表达式。

因此您可以执行以下操作:

myMap.getOrElse(myKey, throw new MyCustomException("Custom Message HERE")

【讨论】:

    猜你喜欢
    • 2015-06-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-16
    • 2013-06-24
    • 1970-01-01
    相关资源
    最近更新 更多