Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best Practices for nested Scala Option fields?

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?

like image 311
GroomedGorilla Avatar asked Oct 15 '25 17:10

GroomedGorilla


2 Answers

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.

like image 148
Régis Jean-Gilles Avatar answered Oct 18 '25 05:10

Régis Jean-Gilles


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

like image 37
sarveshseri Avatar answered Oct 18 '25 06:10

sarveshseri



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!