Scala Option object inside another Option object

后端 未结 4 2005
情书的邮戳
情书的邮戳 2021-02-08 02:05

I have a model, which has some Option fields, which contain another Option fields. For example:

case class First(second: Option[Second], name: Option[String])
ca         


        
相关标签:
4条回答
  • 2021-02-08 02:56

    The solution is to use Option.map and Option.flatMap:

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

    Or the equivalent (see the update at the end of this answer):

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

    This returns an Option[Int] (provided that numberOfSmth returns an Int). If any of the options in the call chain is None, the result will be None, otherwise it will be Some(count) where count is the value returned by numberOfSmth.

    Of course this can get ugly very fast. For this reason scala supports for comprehensions as a syntactic sugar. The above can be rewritten as:

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

    Which is arguably nicer (especially if you are not yet used to seeing map/flatMap everywhere, as will certainly be the case after a while using scala), and generates the exact same code under the hood.

    For more background, you may check this other question: What is Scala's yield?

    UPDATE: Thanks to Ben James for pointing out that flatMap is associative. In other words x flatMap(y flatMap z))) is the same as x flatMap y flatMap z. While the latter is usually not shorter, it has the advantage of avoiding any nesting, which is easier to follow.

    Here is some illustration in the REPL (the 4 styles are equivalent, with the first two using flatMap nesting, the other two using flat chains of 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)
    
    0 讨论(0)
  • 2021-02-08 02:56

    I think it is an overkill for your problem but just as a general reference:

    This nested access problem is addressed by a concept called Lenses. They provide a nice mechanism to access nested data types by simple composition. As introduction you might want to check for instance this SO answer or this tutorial. The question whether it makes sense to use Lenses in your case is whether you also have to perform a lot of updates in you nested option structure (note: update not in the mutable sense, but returning a new modified but immutable instance). Without Lenses this leads to lengthy nested case class copy code. If you do not have to update at all, I would stick to om-nom-nom's suggestion.

    0 讨论(0)
  • 2021-02-08 02:57

    There is no need for scalaz:

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

    Alternatively you can use nested flatMaps

    0 讨论(0)
  • 2021-02-08 03:02

    This can be done by chaining calls to flatMap:

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

    You can also do this with a for-comprehension, but it's more verbose as it forces you to name each intermediate value:

    def getN(first: Option[First]): Option[Int] =
      for {
        f <- first
        s <- f.second
        t <- s.third
        n <- t.numberOfSmth
      } yield n
    
    0 讨论(0)
提交回复
热议问题