2012-08-05 40 views
5

我试图将两个Option[Iterable[_]]合并为一个新的Option[Iterable[_]]。如果其中一个(或两个)元素是Some和None,我想返回一个Some。似乎应该有一个这样做的惯用方式,但我似乎无法找到一个。以下似乎做我想要的,但不是我想要的那种光滑的解决方案。组合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 
} 

任何提示将不胜感激。谢谢!

+0

那种近乎类似的问题: http://stackoverflow.com/questions/10617979/binary-operator-with-option-arguments/10618340#10618340,可能有帮助 – 2012-08-06 02:11:08

回答

11

如果你愿意忍受有点抽象代数的,有一个很好的概括这里:Iterable[_]monoid在连接下,monoid只是一组事物(在这种情况下是可迭代的集合),以及带有一些简单属性和标识元素(空集合)的类加法操作(连接)。

同样,如果A是幺,那么Option[A]也是在你merge的一个稍微更一般的幺半群:

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

(请注意,我们所需要的事实A是幺知道,在第一线做)

Scalaz library捕捉其Monoid类型的类,它可以让所有这些概括你写你的merge这样的:

import scalaz._, Scalaz._ 

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

预期其中一期工程:

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 

(请注意,有,将给予有效Monoid实例为IterableOption等操作,但你是最常用的,而且Scalaz提供由那些默认情况下)

+0

很好的答案。认为第一个代码片段的第三行有一个小错字,应该是== Some(ys)? – 2012-08-06 10:11:44

+0

@BrianSmith:当然,是的,谢谢! – 2012-08-06 11:01:47

3

这工作:

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

for/yield部分将增加的选项的内容,当且仅当两者都Some

您也可以删除一些网点和括号的,如果你想:

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

啊,是的。这太好了 - 谢谢。 – robo 2012-08-05 22:56:36

1

您可以使用此任意元数:

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