2016-05-19 10 views
6

Ho la seguente classe:Come unire due variabili Option [String] in una quando si usa flatMap in Scala?

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

Ora voglio rimuovere l'attributo fullName perché è ridondante. Tuttavia, ho un metodo nella mia classe User che restituisce l'fullName:

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

Ora sto cercando di sostituire la parte .flatMap(_.fullName) con una concatenazione di firstName + lastName. Come si può fare? Ho bisogno di fare una nuova Option[String], in questo modo:

def fullName(loginInfo:LoginInfo) = { 
    val firstName = profileFor(loginInfo).flatMap(_.firstName) 
    val lastName = profileFor(loginInfo).flatMap(_.lastName) 
    val fullName : Option[String] = Some(firstName + " " + lastName) 
    fullName 
} 

risposta

3

Penso che sia una buona applicazione di 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" 
} 
+0

Grazie! Mi piace molto questo approccio. 'last <- profile.firstName' dovrebbe essere' last <- profile.lastName' invece. –

+0

Restituisce 'None' se' firstName' o 'lastName' è' None' invece di lasciarlo vuoto e inserire lo spazio di separazione solo se necessario, come nella risposta di Synesso. –

+0

Forse ho frainteso la domanda, ma questo risulterebbe in Nessuno se il primo o il cognome erano Nessuno. – Synesso

4

Ecco uno approccio

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

test rapido nel 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) 
+0

Grazie per il vostro aiuto. Tuttavia, se sto usando il tuo approccio suggerito, dovrei comunque usare '.flatMap' due volte per ottenere' firstName' e 'lastName' e quindi fare un' List' o c'è un approccio migliore? –

+0

Non c'è bisogno di 'flatMap' in questa risposta. Basta creare un elenco di 'Opzioni [String]' e quindi appiattirlo. –

2

map2 (vedi capitolo 4 di "the red book") vi offre un po 'di astrazione:

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) 

Poi, lasciando la roba LoginInfo out (perché non è stato definito profileFor ovunque), si può semplicemente definiamo fullName come

def fullName: Option[String] = map2(firstName, lastName) { _ + " " + _ } 
+0

Stessa osservazione di un'altra risposta: restituisce "Nessuno" se "firstName" o "lastName" è "None" invece di lasciarlo vuoto e inserire lo spazio di separazione solo se necessario, come nella risposta di Synesso. –

+0

@ Jean-PhilippePellet corretto. Comunque sia, ciò che descrivi è che il comportamento desiderato non è chiaro per me. – Jubobs

1

possiamo trattare Option come una collezione e g et quello che stai cercando in un semplice one-liner:

val firstName: Option[String] = Some("John") 
val lastName: Option[String] = Some("Doe") 
val fullName: Option[String] = (firstName ++ lastName).reduceOption(_ + " " + _) // Some("John Doe") 
0

penso che dal punto di vista OOP, si dovrebbe tenere in fullNameProfile classe, ma come un metodo e dalla classe User solo delegare a Profile.

È possibile utilizzare qualsiasi soluzione già fornita, inserirò il mio, utilizzando scalaz e builder applicativo, è simile a map2, solo più generale, come è possibile unire qualsiasi (beh, non proprio nessuno, ma un importo ragionevole come devi passare la funzione che richiede lo stesso numero di argomenti del numero di oggetti che vuoi applicare a) numero di opzioni o altri applicativi.

@ 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 
1

realtà ho scritto un blog post non molto tempo fa con un paio di opzioni native Scala di farlo.

Quello che mi è piaciuto di più è simile a @Pawels answer solo con 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) 

Questo approccio è bello perché funziona quando una delle Option[T] è 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) 

Un'altra bella proprietà di questo è che può funzionare per gli elementi N quando si utilizzano varargs:

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)