【问题标题】:Scala Option object inside another Option object另一个 Option 对象内的 Scala Option 对象
【发布时间】:2013-02-13 05:29:49
【问题描述】:

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

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,但没有找到更好的方法来做到这一点。

有什么想法吗? 提前致谢。

【问题讨论】:

  • 只是一个注释,但 flatMap 不会像下面给出的那样工作几次。它应该是 First.second.flatMap(_.third.flatMap(_.numberOfSmth)).get 并且仍然可能抛出异常
  • 确实,谢谢。谢谢大家的回答,我找到了我要找的东西。

标签: scala option monads scalaz


【解决方案1】:

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

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

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

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)

【讨论】:

  • 你不需要使用丑陋的嵌套:因为flatMap的关联性,a flatMap (b flatMap c)相当于a flatMap b flatMap c
  • 谢谢,你说的很对。我通常嵌套它们,因为这模仿了被映射/平面映射的实际结构(在这种情况下 嵌套的)。但确实,它作为flatMap 的扁平链更具可读性。
  • 请注意,不嵌套 flatMaps 的缺点是不能在嵌套的 lambda 表达式中使用 lambda 参数,因此尽管它在这种情况下有效,但我不推荐它。它也更慢并且创建了更多的函数对象。
  • 我只能同意。至于为什么有人想知道为什么嵌套版本更快并且创建的函数对象更少(平均而言),这是因为一旦我们遇到None,链就会停止,所以我们不会(不必要地)创建功能对象在链的下游。使用flatMap 的扁平链,所有flatMaps 总是在我们遇到None 之后执行,所以这是浪费的处理加上不必要的函数对象创建(那些过去我们遇到@ 987654348@)。所以很高兴知道这一点,即使在大多数情况下这并不重要。
  • 感谢 REPL 示例!能和它一起玩真是太好了。
【解决方案2】:

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

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

【讨论】:

  • @Downvoter:您介意解释一下否决票吗?鉴于 OP 必须大量更新此嵌套结构,我认为应该允许 OP 将 Lenses 的概念作为替代解决方案?
【解决方案3】:

不需要scalaz:

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

您也可以使用嵌套的 flatMaps

【讨论】:

    【解决方案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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-01-17
      • 1970-01-01
      • 2019-05-18
      • 2019-01-17
      • 2016-11-08
      • 2016-08-01
      • 1970-01-01
      • 2013-09-30
      相关资源
      最近更新 更多