2012-08-05 78 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)