结合Scala选项[Iterable [_]]

时间:2012-08-05 22:39:02

标签: scala collections monads

我正在尝试将两个Option[Iterable[_]]合并为一个新的Option[Iterable[_]]。如果其中一个(或两个)元素是Some和None,我想返回一个。似乎应该有一种惯用的做法,但我似乎无法找到一个。以下似乎做了我想要的,但不是我希望的光滑解决方案。

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
}

任何提示都表示赞赏。谢谢!

3 个答案:

答案 0 :(得分: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

(请注意,还有其他操作可以为MonoidIterable提供有效的Option个实例,但是您的最常用,以及Scalaz默认提供的实例。 )

答案 1 :(得分: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

答案 2 :(得分:1)

你可以将它用于任意的arity:

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