Bottom of the Rabbit Hole: for-comprehensions and monads

case class Contact(id: Int, name: String, phone: Option[String], 
email: Option[String], friendIds: List[Int])
def dbLookup(id: Int): Try[Contact] = ???
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
}
def inviteFriends: List[Int] =
friendIds
.flatMap(id => dbLookup(id).toOption
.flatMap(contact => contact.email)
.filter(sendEmail).map(_ => id))

Introducing for-comprehensions

for (id <- friendIds) {
???
}
def inviteFriends: List[Int] = {

val successes = for {
id <- friendIds
contact <- dbLookup(id).toOption
emailAddr <- contact.email
if sendEmail(email)
} yield(id)

successes
}
def inviteFriends: List[Int] = {
for {
id <- friendIds
contact <- dbLookup(id).toOption
emailAddr <- contact.email
if sendEmail(emailAddr)
} yield(id)
}
  • We iterate through the friendIds, calling each one id.
  • We call dbLookup on the id, and convert its resulting Try into an Option, assigning it’s wrapped value to something named contact. (In other words, if the dbLookup doesn’t throw an error, its returned Contact object will be assigned to contact.
  • If the optionalcontact.email field has a value, it will be assigned to emailAddr.
  • And if sendEmail returns true…
  • We’ll yield the id from the top of our loop logic.
val successes = friendIds
.flatMap(id => dbLookup(id).toOption
.flatMap(contact => contact.email)
.filter(sendEmail).map(_ => id))
val successes = friendIds
.flatMap(id => dbLookup(id).toOption
.map(contact => contact.email)
.filter(sendEmail).map(_ => id))
def inviteFriends: List[Int] = {
for {
id <- friendIds
contact <- dbLookup(id).toOption
emailAddr = contact.email
if sendEmail(emailAddr)
} yield(id)
}
  • id <- friendIds rewrites to a flatMap
  • emailAddr = contact.email rewrites to a map
  • if sendEmail(emailAddr) rewrites to a filter—not the lack of parenthesis around the condition
  • And finally, for yield(???) the contents of the yield is a function is put into a final .map operation

Unveiling my Evil Master Plan

  • A monad is a kind of typeclass that “boxes” any other type.
  • It has .map that applies functions to the contents of the boxes.
  • It has .flatMap for functions that create their own boxes in order to avoid nesting problems.
  • Option, Try, Either and even Future can be considered good examples of monads. (For the latter, let’s not talk about referential transparency, please.)
  • They isolate “pure functions” from problematic contexts like asynchronous operations, exception handling, missing data, etc.

--

--

--

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

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Understanding The Core Aspects Of Singleton Design Pattern

5 Factors to Choosing the Right Business Processes to Automate

Welcome to the forest!

Twilio-Zoho Desk SMS Integration

Electronics note-04: Low Pass Filters and High Pass Filters explained

Install Ubuntu on Windows 10 Pro

How to write tests with Pytest-Selenium plugin

Why GoodData Decided to Integrate With Dremio

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

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

More from Medium

Scala 3: create an sbt project with subprojects and build the fat jar

Understanding the “Found ZIO.Task Required ZIO.ZIO” dilemma

Journey of Implicits in Scala — part 2

Type Class Derivation in Scala 3