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