12

我有一个模型,它有一些选项字段,其中包含另一个选项字段。例如:

case class First(second: Option[Second], name: Option[String])
case class Second(third: Option[Third], title: Option[String])
case class Third(numberOfSmth: Option[Int])

我从外部 JSON 接收这些数据,有时这些数据可能包含 null,这就是这种模型设计的原因。

所以问题是:获得最深领域的最佳方法是什么?

First.get.second.get.third.get.numberOfSmth.get

上面的方法看起来真的很难看,如果其中一个对象为 None,它可能会导致异常。我正在寻找 Scalaz lib,但没有找到更好的方法来做到这一点。

有任何想法吗?提前致谢。

4

4 回答 4

20

解决方案是使用Option.mapand Option.flatMap

First.flatMap(_.second.flatMap(_.third.map(_.numberOfSmth)))

或等价物(请参阅此答案末尾的更新):

First flatMap(_.second) flatMap(_.third) map(_.numberOfSmth)

这将返回一个Option[Int](前提是numberOfSmth返回一个Int)。如果调用链中的任何选项是None,则结果将是None,否则它将是Some(count)wherecount的返回值numberOfSmth

当然,这会很快变得丑陋。出于这个原因,scala 支持将理解作为语法糖。上式可以改写为:

for { 
  first <- First
  second <- first .second
  third <- second.third
} third.numberOfSmth

可以说哪个更好(特别是如果您还不习惯在任何地方看到map/ flatMap,使用 scala 一段时间后肯定会出现这种情况),并在引擎盖下生成完全相同的代码。

有关更多背景信息,您可以查看另一个问题:Scala 的产量是多少?

更新:感谢 Ben James 指出 flatMap 是关联的。换句话说x flatMap(y flatMap z))),与 相同x flatMap y flatMap z。虽然后者通常不短,但它具有避免任何嵌套的优点,这更容易遵循。

这是 REPL 中的一些说明(4 种样式是等价的,前两种使用 flatMap 嵌套,另外两种使用 flatMap 的扁平链):

scala> val l = Some(1,Some(2,Some(3,"aze")))
l: Some[(Int, Some[(Int, Some[(Int, String)])])] = Some((1,Some((2,Some((3,aze))))))
scala> l.flatMap(_._2.flatMap(_._2.map(_._2)))
res22: Option[String] = Some(aze)
scala> l flatMap(_._2 flatMap(_._2 map(_._2)))
res23: Option[String] = Some(aze)
scala> l flatMap(_._2) flatMap(_._2) map(_._2)
res24: Option[String] = Some(aze)
scala> l.flatMap(_._2).flatMap(_._2).map(_._2)
res25: Option[String] = Some(aze)
于 2013-02-27T12:05:58.183 回答
10

不需要scalaz:

for { 
  first  <- yourFirst
  second <- f.second
  third  <- second.third
  number <- third.numberOfSmth
} yield number

或者,您可以使用嵌套的 flatMaps

于 2013-02-27T12:03:37.810 回答
4

这可以通过链接调用来完成flatMap

def getN(first: Option[First]): Option[Int] =
  first flatMap (_.second) flatMap (_.third) flatMap (_.numberOfSmth)

您也可以使用 for-comprehension 来执行此操作,但它更冗长,因为它会强制您命名每个中间值:

def getN(first: Option[First]): Option[Int] =
  for {
    f <- first
    s <- f.second
    t <- s.third
    n <- t.numberOfSmth
  } yield n
于 2013-02-27T12:03:23.273 回答
1

我认为这对您的问题来说是一种矫枉过正,但只是作为一般参考:

这个嵌套访问问题通过一个称为 Lenses 的概念来解决。它们提供了一种通过简单组合访问嵌套数据类型的良好机制。作为介绍,您可能想要检查例如这个 SO answerthis tutorial。在您的情况下使用 Lenses 是否有意义的问题是您是否还必须在嵌套选项结构中执行大量更新(注意:更新不是可变的,而是返回一个新的修改但不可变的实例)。如果没有 Lenses,这会导致冗长的嵌套案例类copy代码。如果您根本不需要更新,我会坚持om-nom-nom 的建议

于 2013-02-27T12:04:27.860 回答