Я хочу преобразовать a List[Option[T]]
в a Option[List[T]]
. Тип подписи функции
def lo2ol[T](lo: List[Option[T]]): Option[List[T]]
Ожидаемое поведение состоит в том, чтобы отобразить список, который содержит только Some
s в a 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
}
}}
Есть функция, которая превращает Список [Вариант [A]]
в Вариант [Список [A]]
в Scalaz. Это последовательность
. Чтобы получить Нет
в случае, если какой-либо из элементов - Нет
и Некоторый [Список [A]]
, если все элементы - Некоторые
], вы можете просто сделать это:
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
, то последовательность
также будет Нет
. Если вы хотите получить список всех значений Some
, независимо от того, являются ли другие значения None
, вы можете сделать следующее:
lo flatMap (_.toList)
Вы можете обобщить это для любого Монада
, которая также образует Моноид
( Список
является одним из них):
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))
Хотя Applicative[Option]
в Scalaz имеет неправильное поведение для прямого использования MA#sequence
, вы также можете вывести Applicative
из Monoid
. Это удобно делать с помощью MA#foldMapDefault
или MA#collapse
.
В данном случае мы используем моноид[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, либо отправляет компилятор в бесконечный цикл.
UPDATE
Чтобы не обходить список дважды, я должен был использовать foldMapDefault
:
(List(some(1), none[Int], some(2)) foldMapDefault (_ ∘ ((_: Int).pure[List])))
Этот ответ был основан на первоначальном запросе, что пустой список или список, содержащий только None
ы, должен возвращать None
. Кстати, это лучше всего моделируется типом Option[scalaz.NonEmptyList]
-- NonEmptyList
гарантирует по крайней мере один элемент.
Если вам нужен просто List[Int]
, есть много более простых способов, приведенных в других ответах. Два прямых способа, которые не были упомянуты:
list collect { case Some(x) => x }
list flatten
По какой-то причине вам не нравится
if (lo.exists(_ isEmpty)) None else Some(lo.map(_.get))
? Это, наверное, самый короткий в Scala без Scalaz.