2012-08-05 4 views
5

Sto provando a combinare due Option[Iterable[_]] in un nuovo Option[Iterable[_]]. Vorrei restituire un Qualcuno se uno (o entrambi) degli elementi è un Qualcuno e un Nessuno altrimenti. Sembra che ci dovrebbe essere un modo idiomatico di farlo, ma non riesco a trovarne uno. Quello che segue sembra fare quello che voglio, ma non è proprio la soluzione perfetta che speravo.Combinazione dell'opzione Scala [Iterable [_]]

def merge(
    i1: Option[Iterable[_]], i2: Option[Iterable[_]] 
): Option[Iterable[_]] = (i1, i2) match { 
    case (Some(as), Some(bs)) => Some(as ++ bs) 
    case (a @ Some(as), None) => a 
    case (None, b @ Some(bs)) => b 
    case _ => None 
} 

Qualsiasi consiglio è apprezzato. Grazie!

+0

domanda Tipo di quasi simile: http://stackoverflow.com/questions/10617979/binary-operator-with-option-arguments/10618340#10618340, potrebbe essere utile –

risposta

11

Se siete disposti a mettere in su con un po 'di algebra astratta, c'è una bella generalizzazione qui: Iterable[_] è un monoid sotto concatenazione, dove un monoid è solo un insieme di cose (collezioni iterabili, in questo caso) e un'operazione di tipo addizione (concatenazione) con alcune proprietà semplici e un elemento di identità (la raccolta vuota).

Allo stesso modo, se A è un monoide, poi Option[A] è anche un monoide sotto una versione leggermente più generale della vostra merge:

Some(xs) + Some(ys) == Some(xs + ys) 
Some(xs) + None  == Some(xs) 
None  + Some(ys) == Some(ys) 
None  + None  == None 

(Si noti che abbiamo bisogno del fatto che A è un monoide di sapere che cosa . da fare in prima linea)

il Scalaz library cattura tutte queste generalizzazioni nella sua classe Monoid tipo, che consente di scrivere il vostro merge in questo modo:

012.
import scalaz._, Scalaz._ 

def merge(i1: Option[Iterable[_]], i2: Option[Iterable[_]]) = i1 |+| i2 

che funziona come previsto:

scala> merge(Some(1 to 5), None) 
res0: Option[Iterable[_]] = Some(Range(1, 2, 3, 4, 5)) 

scala> merge(Some(1 to 5), Some(4 :: 3 :: 2 :: 1 :: Nil)) 
res1: Option[Iterable[_]] = Some(Vector(1, 2, 3, 4, 5, 4, 3, 2, 1)) 

scala> merge(None, None) 
res2: Option[Iterable[_]] = None 

(Si noti che ci sono altre operazioni che darebbe validi Monoid istanze per Iterable e Option, ma la tua sono i più comunemente utilizzati, e quelli che Scalaz fornisce da predefinito)

+0

Ottima risposta. Pensa che c'è un errore di battitura nella terza riga del primo frammento di codice, dovrebbe essere == Some (ys)? –

+0

@BrianSmith: Sì, certo, grazie per aver catturato! –

3

Questo funziona:

def merge(i1: Option[Iterable[_]], i2: Option[Iterable[_]]): Option[Iterable[_]] = 
    (for (a <- i1; b <- i2) yield a ++ b).orElse(i1).orElse(i2) 

La porzione for/yield aggiungerà il contenuto delle opzioni se e solo se entrambi sono Some.

Si può anche cadere alcuni dei punti e parentesi se si desidera:

(for (a <- i1; b <- i2) yield a ++ b) orElse i1 orElse i2 
+0

Ah, sì. È molto più bello - grazie. – robo

1

Si potrebbe utilizzare questo per arietà arbitrario:.

def merge(xs: Option[Iterable[_]]*) = 
    if (xs.forall(_.isEmpty)) None else Some(xs.flatten.flatten)