Convertir una Lista de Opciones, una Opción de la Lista utilizando Scalaz
Pregunta
Quiero transformar una List[Option[T]]
en un Option[List[T]]
.El tipo de firma de la función es
def lo2ol[T](lo: List[Option[T]]): Option[List[T]]
El comportamiento esperado es que el mapa muestra una lista que contiene sólo Some
s en una Some
contiene una lista de los elementos en el interior de los elementos Some
's.Por otro lado, si la entrada de la lista tiene al menos un None
, el comportamiento esperado es que acabo de volver de None
.Por ejemplo:
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())
Un ejemplo de implementación, sin scalaz, sería:
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]];
}}}
Yo recuerdo haber visto en algún lugar un ejemplo similar, pero utilizando Scalaz para simplificar el código.¿Cómo sería?
Un poco más sucinta versión, con Scala2.8 PartialFunction.condOpt
, pero aún sin 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
}
}}
Solución
Hay una función que convierte una List[Option[A]]
en un Option[List[A]]
en Scalaz. Es sequence
. Para obtener None
en caso de que alguno de los elementos son None
y una Some[List[A]]
en caso de que todos los elementos son Some
, sólo puede hacer esto:
import scalaz.syntax.traverse._
import scalaz.std.list._
import scalaz.std.option._
lo.sequence
Este método se convierte en realidad F[G[A]
en G[F[A]]
dado que existe una implementación de Traverse[F]
, y de Applicative[G]
(Option
y List
sucede para satisfacer ambos y se proporcionan por esas importaciones).
La semántica de Applicative[Option]
son tales que si cualquiera de los elementos de un List
de Option
s son None
, entonces el sequence
será None
también. Si desea obtener una lista de todos los valores Some
independientemente de si otros valores son None
, usted puede hacer esto:
lo flatMap (_.toList)
Se puede generalizar que para cualquier Monad
que también forma una Monoid
(List
pasa a ser uno de ellos):
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))
Otros consejos
Por alguna razón no te gusta
if (lo.exists(_ isEmpty)) None else Some(lo.map(_.get))
?Esa es probablemente la más corta en la Scala sin Scalaz.
Mientras que el Applicative[Option]
en Scalaz tiene el comportamiento erróneo directamente el uso MA#sequence
, también se puede derivar una Applicative
de un Monoid
. Esto se hizo práctica y MA#foldMapDefault
o MA#collapse
.
En este caso, se utiliza un Monoid[Option[List[Int]]
. En primer lugar, realizamos un mapa interior (MA#∘∘
) para envolver los Int
s individuales en List
s de un elemento.
(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]]
abstracción de List
a cualquier recipiente con instancias para Traverse
, Pointed
y 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]]
Lamentablemente, tratando de compilar este código, ya sea actualmente desencadenantes # 2741 o envía el compilador en un bucle infinito.
Actualizar
Para evitar que atraviesa la lista dos veces, que debería tener foldMapDefault
usada:
(List(some(1), none[Int], some(2)) foldMapDefault (_ ∘ ((_: Int).pure[List])))
Esta respuesta se basa en la solicitud original que una lista vacía, o una lista que contiene sólo None
s, deben devolver un None
. Por cierto, este sería mejor modelada por la Option[scalaz.NonEmptyList]
tipo -. Garantías NonEmptyList
al menos un elemento
Si lo que desea es la de un List[Int]
, hay muchas formas más sencillas, dadas en otras respuestas. Dos formas directas que no se han mencionado:
list collect { case Some(x) => x }
list flatten
Esto funcionó para mí. Espero que esto sea una solución correcta.
Se devuelve None si una de las opciones de la lista en su defecto, de lo contrario devuelve la opción de Lista [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
}
}
}
A partir Scala 2.13
, y la adición de la Option::unless
constructor para la biblioteca estándar , una variante a Rex de Kerr respuesta sería:
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
o, si el rendimiento está en juego (en orden a la conversión implícita evitar de flatten
de Option
a List
):
Option.unless(list contains None)(list.map(_.get))