获取选项值或引发异常


76

给定一个Option,获得其价值或引发异常尝试的惯用方式是什么?

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

12
为什么不.get呢?
神秘丹

@MyseriousDan我偶然发现了这个案例。我有一种情况,我绝对100%肯定它必须返回Some,否则,这是一个严重的问题,必须抛出。但是我想抛出自己的异常和任何模棱两可NoSuchElementException的异常。同样,在大多数情况下,此方法可能会安全地返回None。
Kai Sellgren 2014年

1
真正的问题是为什么您的类型说您知道它可能会丢失,但为什么却没有。如果您可以用类型表达您的知识,那通常是最好的。它可能涉及到一些重塑您的班级,但是从长远来看通常会更令人愉快。
神秘的丹

1
@TravisBrown的答案应该是被接受的,而不是我的。我的不是惯用语言,而是在我还是“新手”时写的(我仍然是,但是是2级)。让我们举个例子,不要做什么... ;-)
Sebastian N.

如果必须抛出异常,那是错误的。使用Option的惯用方式是返回None而不根本不抛出异常。
Priyank Desai

Answers:


46

(编辑:这不是最好的或最惯用的方式。我在不熟悉Scala的时候写了它。我在这里留了一个不怎么做的示例。如今,我将作为@TravisBrown来做。)

我认为这可以归结为两点:

  • 您如何确定其中的价值?
  • 如果不是,您想如何反应

如果那时您希望代码中存在该值,而在远程情况下您不希望您的程序快速失败,那么我只会做一个普通的操作getNoSuchElementException如果没有值,则让Scala抛出a :

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

如果您想以不同的方式处理案件(引发您自己的异常),我认为一种更优雅的方式如下所示:

def foo():字符串= {
  val x:Option [String] =无
  x匹配{
    情况Some(value)=> value
    情况None =>抛出新的MyRuntimeException(“ blah”)
  }
} 

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

def foo():字符串= {
  val x:Option [String] =无
  x.getOrElse(“我的替代值”)
} 

当然,应该注意的是,匹配Somes(值)只是为了提取相同的值并不是惯用的Scala,getOrElse如果这是所有人想要做的,那应该是首选。我只想举例说明“如果已定义,则求值为X,否则,则抛出异常”的情况。
Sebastian N.

1
引用纪尧姆贝尔罗斯:“我想我还是在业余小时,然后:-)”
塞巴斯蒂安N.

14
您还可以通过引发以下异常来组合@SebastianN。的建议getOrElsex.getOrElse(throw new MyRuntimeException("message"))
Jona 2015年

4
@TravisBrown:在获得更多Scala经验之后,我不得不说你是对的。在这样的Option上进行匹配而不是使用getOrElse是不必要的,而且不是惯用的。您应该是公认的答案。
塞巴斯蒂安N.15年

131

一个throw“说法”确实是在Scala中的表达,它有型Nothing,这是每一个其他类型的子类型。这意味着您可以只使用普通的old getOrElse

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

不过,您确实不应该这样做。


为什么不?什么是更优雅/惯用的方式?模式匹配?
Sebastian N.

7
如果将故障始终作为值建模到整个程序中,那么类型系统将获得更多收益(并为真正的特殊问题(例如OutOfMemoryError)保存异常)。
特拉维斯·布朗

23
我看不到将失败建模为任何无法恢复的问题(OOME显然是其中之一)的价值的优势。以从配置文件中读取元数据为例。正确执行程序需要格式正确的配置文件。如果由于用户错误而导致配置文件中包含无效条目,则程序无法继续。因此,在这种情况下,展开堆栈并退出是合适的。
乔纳森·诺伊费尔德

1
@JonathanNeufeld特拉维斯·布朗(Travis Brown)的评论听起来像是一种更为纯粹的纯粹主义者方法。不是每个人/每个用例都适合。我对您的看法更多,只是保释,不要对逻辑/奇特类型的系统进行过多处理。
StephenBoesch

我不认为@TravisBrown是一个纯粹主义者,他在SO上的其他答案表明并非如此。但是,抛出或建模失败确实是上下文问题。在许多情况下,应用程序故障不是一种选择(而是针对不可恢复的错误,例如OOME)。在这种情况下,故障管理应该是整个代码中的模型,并且可以通过设计进行跟踪。
Juh_ '18

14

只需使用.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)

避免您的特定问题。


4
我以为OP希望对所引发的异常进行更多控制,但是即使不是,get这也不是个好主意-它只是说明您所做的事情令人不快。getOrElse当您确定这不仅是一次性代码并希望使其更安全时,更难以错过使用并明确引发异常的情况。
特拉维斯·布朗

当然,基本上,在我看来,除非真的需要,否则永远不要使用任何类型的get on选项。我将使用foreach或map使用选项进行计算。
Felix

11

我希望这将帮助您了解如何使用类型来表示错误(通常是效果)。

功能性Scala中的错误处理策略

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

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

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

  • 将您的错误建模为ADT(简而言之类型类型层次结构)以使用它,例如,在“任意一个”的左侧,以表示更复杂的错误情况。

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

  • 使用Either.catchOnlyorTryCats.IO(高级)而不是catch块来处理意外的故障。提示:您仍然可以使用ADT,但可以将它们从throwables扩展。关于Eithervs的Try更多信息。

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

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

这样的方法将简化代码的维护和更新,因为您可以在不考虑实现细节的情况下进行推理(又称本地推理)。另外-减少错误-您不能错过类型错误。并且使程序的编写更容易(在mapflatMap和其他组合器的帮助下)-因为它在类型级别上更简单,而不是在非本地异常和副作用下。有关学习功能性Scala的
更多信息。


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

  • 使用mapflatMap与模式匹配组合来构成这种类型的不同的值,例如:
x.faltMap { case Right(Some(v)) => anotherFuture(v); case Left(er) => ... }
  • 如果这样做没有帮助,您可以尝试使用MonadTransformers(不要害怕它的名称,它只是包装了Either和Future之类的效果类型)
  • 另外,一种选择是通过将错误的ADT从Throwable扩展为与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))
    }
}

相当不错的总结。您在句子中间没有留下最后的“想法”;)
StephenBoesch

@YordanGeorgiev链接已损坏,已删除
Albert Bikeev

0

Scala现在支持使用getOrElse()方法在地图上进行此操作,请参见此处的文档

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

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

myMap.getOrElse(myKey, throw new MyCustomException("Custom Message HERE")
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.