另一个Option对象中的Scala Option对象

时间:2013-02-27 11:59:54

标签: scala option monads scalaz

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

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 个答案:

答案 0 :(得分:17)

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

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

或等效内容(请参阅本答案末尾的更新):

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

这会返回Option[Int](假设numberOfSmth返回Int)。如果调用链中的任何选项为None,则结果为None,否则为Some(count),其中countnumberOfSmth返回的值}。

当然,这可能会非常快。出于这个原因,scala支持 for comprehensions 作为语法糖。以上内容可以改写为:

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

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

有关更多背景信息,您可以查看其他问题:What is Scala's yield?

<强>更新: 感谢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)

答案 1 :(得分:10)

不需要scalaz:

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

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

答案 2 :(得分: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

答案 3 :(得分:0)

我认为这对您的问题来说太过分了,但仅作为一般参考:

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