我想将List[Option[T]]
变成Option[List[T]]
。函数的签名类型是
def lo2ol[T](lo: List[Option[T]]): Option[List[T]]
预期的行为是将仅包含Some
s的列表映射到包含元素Some
中元素列表的Some
。另一方面,如果输入列表至少有一个None
,则预期的行为是返回None
。例如:
scala> lo2ol(Some(1) :: Some(2) :: Nil)
res10: Option[List[Int]] = Some(List(1, 2))
scala> lo2ol(Some(1) :: None :: Some(2) :: Nil)
res11: Option[List[Int]] = None
scala> lo2ol(Nil : List[Option[Int]])
res12: Option[List[Int]] = Some(List())
没有scalaz的示例实现将是:
def lo2ol[T](lo: List[Option[T]]): Option[List[T]] = {
lo.foldRight[Option[List[T]]](Some(Nil)){(o, ol) => (o, ol) match {
case (Some(x), Some(xs)) => Some(x :: xs);
case _ => None : Option[List[T]];
}}}
我记得在某个地方看过类似的例子,但是使用Scalaz来简化代码。它看起来怎么样?
一个稍微简洁的版本,使用Scala2.8 PartialFunction.condOpt
,但仍然没有Scalaz:
import PartialFunction._
def lo2ol[T](lo: List[Option[T]]): Option[List[T]] = {
lo.foldRight[Option[List[T]]](Some(Nil)){(o, ol) => condOpt(o, ol) {
case (Some(x), Some(xs)) => x :: xs
}
}}
有一个函数可以将List[Option[A]]
变成Scalaz中的Option[List[A]]
。这是sequence
。要获得None
以防任何元素是None
和Some[List[A]]
以防所有元素都是Some
,你可以这样做:
import scalaz.syntax.traverse._
import scalaz.std.list._
import scalaz.std.option._
lo.sequence
这种方法实际上将F[G[A]
变成G[F[A]]
,因为存在Traverse[F]
和Applicative[G]
的实现(Option
和List
碰巧满足这两者并由这些进口提供)。
Applicative[Option]
的语义是这样的,如果List
的Option
的任何元素都是None
,那么sequence
也将是None
。如果你想获得所有Some
值的列表,无论其他值是否为None
,你都可以这样做:
lo flatMap (_.toList)
您可以概括为任何形成Monad
的Monoid
(List
恰好是其中之一):
import scalaz.syntax.monad._
def somes[F[_],A](x: F[Option[A]])
(implicit m: Monad[F], z: Monoid[F[A]]) =
x flatMap (o => o.fold(_.pure[F])(z.zero))
出于某种原因你不喜欢
if (lo.exists(_ isEmpty)) None else Some(lo.map(_.get))
?这可能是没有Scalaz的Scala中最短的。
虽然Scalaz中的Applicative[Option]
有错误的行为直接使用MA#sequence
,你也可以从Applicative
派生出Monoid
。这与MA#foldMapDefault
或MA#collapse
很方便。
在这种情况下,我们使用Monoid[Option[List[Int]]
。我们首先执行内部地图(MA#∘∘
)将单个Int
s包裹在一个元素的List
s中。
(List(some(1), none[Int], some(2)) ∘∘ {(i: Int) => List(i)}).collapse assert_≟ some(List(1, 2))
(List(none[Int]) ∘∘ {(i: Int) => List(i)}).collapse assert_≟ none[List[Int]]
(List[Option[Int]]() ∘∘ {(i: Int) => List(i)}).collapse assert_≟ none[List[Int]]
从List
抽象到任何具有Traverse
,Pointed
和Monoid
实例的容器:
def co2oc[C[_], A](cs: C[Option[A]])
(implicit ct: Traverse[C], cp: Pointed[C], cam: Monoid[C[A]]): Option[C[A]] =
(cs ∘∘ {(_: A).pure[C]}).collapse
co2oc(List(some(1), none[Int], some(2))) assert_≟ some(List(1, 2))
co2oc(Stream(some(1), none[Int], some(2))) assert_≟ some(Stream(1, 2))
co2oc(List(none[Int])) assert_≟ none[List[Int]]
co2oc(List[Option[Int]]()) assert_≟ none[List[Int]]
遗憾的是,尝试编译此代码当前要么触发#2741,要么将编译器发送到无限循环中。
更新为了避免遍历列表两次,我应该使用foldMapDefault
:
(List(some(1), none[Int], some(2)) foldMapDefault (_ ∘ ((_: Int).pure[List])))
这个答案基于原始请求,即空列表或仅包含None
s的列表应返回None
。顺便提一下,这最好用Option[scalaz.NonEmptyList]
类型建模 - NonEmptyList
保证至少有一个元素。
如果你只想要一个List[Int]
,有很多更简单的方法,在其他答案中给出。没有提到的两种直接方式:
list collect { case Some(x) => x }
list flatten
这对我有用。我希望这是一个正确的解决方案。
如果List中的一个Options为None,则返回None,否则返回List [A]的选项
def sequence[A](a: List[Option[A]]): Option[List[A]] = {
a.foldLeft(Option(List[A]())) {
(prev, cur) => {
for {
p <- prev if prev != None
x <- cur
} yield x :: p
}
}
}
启动Scala 2.13
,并将Option::unless
构建器添加到标准库中,Rex Kerr's answer的变体将是:
Option.unless(list contains None)(list.flatten)
// val list = List(Some(1), Some(2)) => Some(List(1, 2))
// val list = List(Some(1), None, Some(2)) => None
或者,如果表现受到威胁(为了避免flatten
隐含从Option
转换为List
):
Option.unless(list contains None)(list.map(_.get))