2012-08-05 2 views
5

J'essaie de combiner deux Option[Iterable[_]] dans un nouveau Option[Iterable[_]]. Je voudrais retourner un certain si un (ou les deux) des éléments est un certain et un aucun autrement. Il semble qu'il devrait y avoir une façon idiomatique de le faire, mais je n'arrive pas à en trouver un. Ce qui suit semble faire ce que je veux, mais ce n'est pas tout à fait la solution que j'espérais.Combinaison de Scala Option [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 
} 

Tous les conseils sont appréciés. Merci!

+0

Type de presque question similaire: http://stackoverflow.com/questions/10617979/binary-operator-with-option-arguments/10618340#10618340, peut être utile –

Répondre

11

Si vous êtes prêt à mettre en place avec un peu d'algèbre abstraite, il y a une belle généralisation ici: Iterable[_] est un monoid sous concaténation, où un monoïde est juste un ensemble de choses (collections itératives, dans ce cas) et une opération de type addition (concaténation) avec quelques propriétés simples et un élément d'identité (la collection vide).

De même, si A est un monoïde, alors Option[A] est aussi un monoïde sous une version légèrement plus générale de votre merge:

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

(Notez que nous avons besoin du fait que A est un monoïde de savoir ce que . à faire dans la première ligne)

le Scalaz library capture toutes ces généralisations dans sa classe de type Monoid, qui vous permet d'écrire votre merge comme ceci:

import scalaz._, Scalaz._ 

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

qui fonctionne comme prévu:

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 

(Notez qu'il existe d'autres opérations qui donneraient des instances Monoid valables pour Iterable et Option, mais le vôtre sont les plus couramment utilisés, et ceux qui Scalaz fournit par par défaut)

+0

Bonne réponse. Pensez qu'il y a une faute de frappe mineure dans la troisième ligne du premier fragment de code, devrait être == Some (ys)? –

+0

@BrianSmith: Oui, bien sûr - merci d'avoir attrapé! –

3

Cela fonctionne:

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

La partie for/yield ajoutera le contenu des options si et seulement si les deux sont Some.

Vous pouvez également déposer quelques-uns des points et des parenthèses si vous voulez:

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

Ah, oui. C'est beaucoup mieux - merci. – robo

1

Vous pouvez l'utiliser pour arité arbitraire.

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