In this case, the most readable solution out of the box (that is, without writing helper methods) would argueably be to chain calls to Option.flatMap
:
objectA flatMap (_.attrB) flatMap (_.attrC.headOption) flatMap (_.attrD)
By using flatMap
, if any of the option in the chain is None
, you'll end up with None
at the end (no exception unlike with get
which will blow when called on None
).
By example:
case class C(attrD: Option[String])
case class B(attrC: List[C])
case class A(attrB: Option[B])
val objectA = Some(A(Some(B(List(C(Some("foo")), C(Some("bar")))))))
// returns Some(foo)
objectA flatMap (_.attrB) flatMap (_.attrC.headOption) flatMap (_.attrD)
val objectA2 = Some(A(Some(B(List()))))
// returns None
objectA2 flatMap (_.attrB) flatMap (_.attrC.headOption) flatMap (_.attrD)
You could also use for comprehensions instead of flatMap
(for comprehension being desugared to chains of flatMap
/map
), but in this case it will actually be less readable (the opposite is usually true) because on each step you'll have to introduce a binding and then reference that on the next step:
for ( a <- objectA; b <- a.attrB; c <- b.attrC.headOption; d <- c.attrD ) yield d
Another solution, if you're willing to use ScalaZ, is to use >>=
in place of flatMap
, which makes it somewhat shorter:
val objectA = Option(A(Some(B(List(C(Some("foo")), C(Some("bar")))))))
// returns Some(foo)
objectA >>= (_.attrB) >>= (_.attrC.headOption) >>= (_.attrD)
This is really exactly the same as using flatMap
, only shorter.