I wonder in Scala, is there any difference/preference between these 2 pieces of code.
// common code
case class Person(maybeAddress: Option[Address])
case class Address(maybeZipCode: Option[String])
chained flatmap:
maybePerson
.flatMap(person => person.maybeAddress)
.flatMap(address => address.maybeZipCode)
nested flatmap:
maybePerson
.flatMap(person => person.maybeAddress.flatMap(address => address.maybeZipCode))
Thanks!
With the example provided there is no much other difference than the style.
If we look at the scaladoc of Option.flatmap
/** Returns the result of applying $f to this $option's value if * this $option is nonempty. * * This is equivalent to: * {{{ * option match { * case Some(x) => f(x) * case None => None * } * }}} */ @inline final def flatMap[B](f: A => Option[B]): Option[B] = if (isEmpty) None else f(this.get)
if we rewrite each example of your question following the pattern matching showed in the scaladoc, for the first case it would be
// from
maybePerson
.flatMap(person => person.maybeAddress)
.flatMap(address => address.maybeZipCode)
// to
maybePerson match {
case Some(person) => person.maybeAddress
case None => None
} match { // this match will always be applied
case Some(address) => address.maybeZipCode
case None => None
}
for the second one would be
// from
maybePerson
.flatMap(person =>
person.maybeAddress
.flatMap(address =>
address.maybeZipCode
)
)
// to
maybePerson match {
case Some(person) =>
// this match will be applied only if maybePerson is Some
person.maybeAddress match {
case Some(address) => address.maybeZipCode
case None => None
}
case None => None
}
As I detailed before, in the example we are analyzing there is no much difference beside the style.
Maybe in a more complex scenario, you would have values that must be computed, some of them could be expensive to process or produce a side effect that you should rollback if something fails.
That being said, from my side I prefer to use for comprehensions
for {
person <- maybePerson
address <- person.maybeAddress
zipCode <- address.maybeZipCode
} yield zipCode
It's a more vertical style and avoids having nested blocks of code, unless you extract the anonymous functions to methods or functions declared in variables like this
def personToAddress(person: Person): Option[Address] =
person.maybeAddress
def addressToZipCode(address: Address): Option[String] =
address.maybeZipCode
// first case
maybePerson
.flatMap(mapToAddress)
.flatMap(mapToZipCode)
// second case
def addressToZipCode(address: Address) =
address.maybeZipCode
def personToZipCode(person: Person) =
person
.maybeAddress
.flatMap(addressToZipCode)
maybePerson
.flatMap(personToZipCode)