Diving deeper into Scala’s Option, Try & Either

case class Contact(id: Int, name: String, phone: Option[String],
email: Option[String], friendIds: List[Int])
def dbLookup(id: Int): Try[Contact]
def sendEmail(email: String): Boolean
  • The database lookup may not be successful (id not present or other technical problems) handled by Try.
  • Once we look up a contact, the record may not have an email address, handled by Option.
  • The email delivery system may fail, and it only reports this by returning false.

The Imperative Approach

A hidden agenda in this article and its predecessor is to give a feel for Functional Programming, which is a major aspect of the Scala language. Granted, you can write Scala code much like you would Java, with vars and loops and leaning on imperative O-O patterns, but the language really sings if you can wrap your head around FP, and I would claim that the use of things like Option is heavily rooted in FP.

def inviteFriends: List[Int] = {

var successes: List[Int] = List.empty

for (id <- friendIds) {
val record = dbLookup(id)
if (record.isSuccess) {
val maybeEmail = record.get.email
if (maybeEmail.isDefined) {
val send = sendEmail(maybeEmail.get)
if (send) successes = id :: successes
}
}
}

successes
}
  • No mutable state—i.e. no vars.
  • No looping (BTW: if you really want to do this broadly, you’ll need to get comfortable with recursion, and for large collections, you’ll have to understand when and how to do tail recursion.)
def inviteFriends: List[Int] = {
val successes = friendIds.map { id =>
dbLookup(id) match {
case Failure(exception) => None
case Success(friend) => {
friend.email match {
case Some(email) => if (sendEmail(email)) Some(id) else None
case None => None
}
}
}
}
successes.filter(_.isDefined).map(_.get)
}

Happy and Unhappy Paths

By the way, it’s worth thinking about the problem as a set of “happy” and “unhappy” paths.

Map to the Rescue

I don’t think my previous article did justice in showing how Map helps us solve our problems.

def inviteFriends: List[Int] = {

val successes = friendIds.map { id =>
dbLookup(id).map { contact =>
contact.email.map { address =>
sendEmail(address)
}
}
}

??? // successes is now of type List[Try[Option[Boolean]]]
}
def inviteFriends: List[Int] = {

val successes = friendIds.map { id =>
dbLookup(id).map { contact =>
contact.email.map { address =>
if (sendEmail(address)) Some(id) else None
}
}
}

??? // successes is now of type List[Try[Option[Option[Int]]]]
}
def inviteFriends: List[Int] = {

val successes = friendIds.map { id =>
dbLookup(id).map { contact =>
contact.email.map { address =>
if (sendEmail(address)) Some(id) else None
}
}
}

success.filter(s => s.isSuccess && s.get.isDefined &&
s.get.get.isDefined)
.map(_.get.get.get)
}

Boxes within Boxes (“It’s turtles all the way down!”)

Think of Option or Try or Either all as boxes. (You’ve heard me quietly refer to them as “typeclasses”, which is sort of the same thing.)

Each of these hold a value of type T in the happy path.

FlatMap to the Rescue!

Because this situation is so common, .map has its own special sibling called .flatMap designed to solve this problem.

Warning: Only use flatMap for the same kinds of boxes!

The one thing you have to be careful about is to make sure you’re working with the same kind of boxes. Option has defined a flatMap function that knows how to deal with functions that create new Options, but we can’t expect it to know how to deal with other boxes, like Try or Either… or even ones we haven’t talked about here, like Future!

Almost Done!

Let’s try to put all this together now. We know we’ll use .toOption to convert the Try box into an Option so we can flatMap everything together.

def inviteFriends: List[Int] = {

val successes = friendIds
.flatMap(id => dbLookup(id).toOption
.flatMap(contact => contact.email)
.filter(sendEmail).map(_ => id))

successes
}

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Murray Todd Williams

Murray Todd Williams

82 Followers

Life-long learner, foodie and wine enthusiast, living in Austin, Texas.