Reputation: 275
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
}
Upvotes: 6
Views: 2967
Reputation: 149656
I actually wrote a blog post not long ago with a couple of native Scala options to do that.
The one I liked the most is similar to @Pawels answer only using reduceLeftOption
:
scala> val firstName = Some("yuval")
firstName: Some[String] = Some(yuval)
scala> val lastName = Some("itzchakov")
lastName: Some[String] = Some(itzchakov)
scala> (firstName ++ lastName).reduceLeftOption((a,b) => s"$a $b")
res10: Option[String] = Some(yuval itzchakov)
This approach is nice because it works when either of the Option[T]
is None
:
scala> val lastName: Option[String] = None
lastName: Option[String] = None
scala> (firstName ++ lastName).reduceLeftOption((a,b) => s"$a $b")
res11: Option[String] = Some(yuval)
Another nice property of this is that it can work for N elements when using varargs as well:
scala> :paste
// Entering paste mode (ctrl-D to finish)
def reduce[T](options: Option[T]*)(f: (T, T) => T) = {
options.flatten.reduceLeftOption(f)
}
reduce(Some(1), Some(1), Some(2), Some(4))(_+_)
// Exiting paste mode, now interpreting.
reduce: [T](options: Option[T]*)(f: (T, T) => T)Option[T]
res0: Option[Int] = Some(8)
Upvotes: 2
Reputation: 8673
I think from oop perspective, you should keep fullName
in Profile
class, but as a method and from your User
class just delegate it to Profile
.
You can use any solution already given, I will post mine, using scalaz and applicative builder, it is similar to map2
, just more general, as you can join any (well, not really any, but a reasonable amount as you have to pass the function that takes same number of arguments as the number of objects you want apply it to) number of options or other applicatives.
@ case class Profile(firstName: Option[String], lastName: Option[String]) {
def fullName: Option[String] = (firstName |@| lastName)(_ + " " + _)
}
@ Profile("A".some, "B".some).fullName
res2: Option[String] = Some("A B")
@ Profile("A".some, none).fullName
res3: Option[String] = None
@ Profile(none, "B".some).fullName
res4: Option[String] = None
@ Profile(none, none).fullName
res5: Option[String] = None
Upvotes: 0
Reputation: 66432
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) { _ + " " + _ }
Upvotes: 2
Reputation: 4471
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")
Upvotes: 2
Reputation: 21740
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"
}
Upvotes: 3
Reputation: 39018
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)
Upvotes: 6