3

I've got a number of nested objects, all wrapped in a Scala Option type. Elsewhere in my project I'm having to call an attribute that is embedded some 5 levels deep (some of which are Lists), each time making a call to .get. This way I end up with something that looks like the following:

objectA.get.attrB.get.attrC.get(0).attrD.get

Other than the series of .get calls (which I'm not sure is ideal), I'm not implementing much error handling this way and should any of the attributes be empty then the whole thing falls apart. Given the nested calls, if I were to limit this to a one-liner as above, I would also only be able to use .getOrElse once at the end.

Are there any suggested ways of working with Option types in Scala?

GroomedGorilla
  • 920
  • 2
  • 10
  • 30

2 Answers2

6

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.

Régis Jean-Gilles
  • 32,541
  • 5
  • 83
  • 97
2

I believe you wanted this,

val deepNestedVal = objectA.get.attrB.get.attrC.get.attrD.get

I think the more preferred way would be to use for-comprehension,

val deepNestedVal = for {
  val1 <- objectA
  val2 <- val1.attrB
  val3 <- val2.attrC
  val4 <- val3.attrD
} yield val4

Other way is to use flatMap as shown in the answer by @Régis Jean-Gilles

sarveshseri
  • 13,738
  • 28
  • 47
  • Thanks! Actually the call is correct in the question, one of the attributes is a List (hence the (0)). Going with @Régis Jean-Gilles answer though since it's more comprehensive and covers for-comprehension too. Cheers – GroomedGorilla Feb 20 '15 at 10:46