Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to merge two Option[String] variables into one when using flatMap in Scala?

I have the following class:

case class Profile(email: Option[String],
                   firstName: Option[String],
                   lastName: Option[String],
                   fullName: Option[String])

Now I want to remove the fullName attribute because it's redundant. However, I have a method in my class User which returns the fullName:

case class User(id: UUID, profiles: List[Profile]) {
// Skipped some lines
  def fullName(loginInfo:LoginInfo) = profileFor(loginInfo).flatMap(_.fullName)
}

Now I am trying to replace the .flatMap(_.fullName) part with a concatenation of firstName + lastName. How can this be done? Do I need make a new Option[String], like this:

def fullName(loginInfo:LoginInfo) = {
  val firstName = profileFor(loginInfo).flatMap(_.firstName)
  val lastName = profileFor(loginInfo).flatMap(_.lastName)
  val fullName : Option[String] = Some(firstName + " " + lastName)
  fullName
}
like image 529
John Doe Avatar asked May 19 '16 08:05

John Doe


4 Answers

Here's one approach

List(firstName, lastName).flatten match {
  case Nil => None
  case xs => Some(xs.mkString(" "))
}

quick testing in the REPL...

scala> def fullName(fn: Option[String], ln: Option[String]): Option[String] = {
     |   List(fn, ln).flatten match {
     |     case Nil => None
     |     case xs => Some(xs.mkString(" "))
     |   }
     | }
fullName: (fn: Option[String], ln: Option[String])Option[String]

scala> fullName(None, None)
res3: Option[String] = None

scala> fullName(Some("a"), None)
res4: Option[String] = Some(a)

scala> fullName(None, Some("b"))
res5: Option[String] = Some(b)

scala> fullName(Some("a"), Some("b"))
res6: Option[String] = Some(a b)
like image 60
Synesso Avatar answered Oct 08 '22 11:10

Synesso


I think that's a good application of a for.

case class User(id: UUID, profiles: List[Profile]) {
// Skipped some lines
  def fullName(loginInfo:LoginInfo): Option[String] = for {
    profile <- profileFor(loginInfo)
    first <- profile.firstName
    last <- profile.lastName
  } yield s"$first $last"
}
like image 32
Reactormonk Avatar answered Oct 08 '22 11:10

Reactormonk


We can treat Option as a collection and get what you're looking for in a simple one-liner:

val firstName: Option[String] = Some("John")
val lastName: Option[String] = Some("Doe")
val fullName: Option[String] = (firstName ++ lastName).reduceOption(_ + " " + _) // Some("John Doe")
like image 2
Paweł Jurczenko Avatar answered Oct 08 '22 10:10

Paweł Jurczenko


map2 (see chapter 4 of "the red book") affords you some abstraction:

def map2[A, B, C](oa: Option[A], ob: Option[B])(f: (A, B) => C): Option[C] =
  for {
    a <- oa
    b <- ob
  } yield f(a, b)

Then, leaving the LoginInfo stuff out (because you didn't define profileFor anywhere), you can simply define fullName as

def fullName: Option[String] = map2(firstName, lastName) { _ + " " + _ }
like image 2
jub0bs Avatar answered Oct 08 '22 11:10

jub0bs