Convertir una Lista de Opciones a una Opción de Lista usando Scalaz
Quiero transformar un List[Option[T]]
en a 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 mapear una lista que contiene solo Some
s en una Some
que contiene una lista de los elementos dentro de los elementos Some
. Por otro lado, si la lista de entrada tiene al menos un None
, el comportamiento esperado es simplemente devolver 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())
Una implementación de ejemplo, 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]];
}}}
Recuerdo haber visto en algún lugar un ejemplo similar, pero usando Scalaz para simplificar el código. ¿Cómo se vería?
Una versión un poco más sucinta, usando 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
}
}}
4 answers
Hay una función que convierte un List[Option[A]]
en un Option[List[A]]
en Scalaz. Es sequence
. Para obtener None
en caso de que cualquiera de los elementos sea None
y a Some[List[A]]
en caso de que todos los elementos sean Some
, simplemente puede hacer esto:
import scalaz.syntax.traverse._
import scalaz.std.list._
import scalaz.std.option._
lo.sequence
Este método en realidad convierte F[G[A]
en G[F[A]]
dado que existe una implementación de Traverse[F]
, y de Applicative[G]
(Option
y List
satisfacer ambos y son proporcionados por esas importaciones).
La semántica de Applicative[Option]
son tales que si alguno 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 cualquier otro valor es None
, puede hacer esto:
lo flatMap (_.toList)
Se puede generalizar que para cualquier Monad
que también forma un Monoid
(List
pasa a ser uno de estos):
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))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-26 13:32:14
Por alguna razón no te gusta
if (lo.exists(_ isEmpty)) None else Some(lo.map(_.get))
? Es probablemente el más corto de Scala sin Scalaz.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-03 00:11:39
Mientras que el Applicative[Option]
en Scalaz tiene el comportamiento incorrecto de usar directamente MA#sequence
, también puede derivar un Applicative
de un Monoid
. Esto se hace conveniente con MA#foldMapDefault
o MA#collapse
.
En este caso, usamos un Monoid[Option[List[Int]]
. Primero realizamos un mapa interno (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 contenedor 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, intentar compilar este código actualmente cualquiera de los disparadores #2741 o envía el compilador a un bucle infinito.
ACTUALIZACIÓN
Para evitar atravesar la lista dos veces, debería haber usado foldMapDefault
:
(List(some(1), none[Int], some(2)) foldMapDefault (_ ∘ ((_: Int).pure[List])))
Esta respuesta se basó en la solicitud original de que una lista vacía, o una lista que solo contenga None
s, debería devolver un None
. Por cierto, esto sería mejor modelado por el tipo Option[scalaz.NonEmptyList]
-- NonEmptyList
garantiza al menos un elemento.
Si solo quieres la a List[Int]
, hay muchas maneras más fáciles, dadas en otros respuesta. Dos formas directas que no se han mencionado:
list collect { case Some(x) => x }
list flatten
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-04 05:58:02
Esto funcionó para mí. Espero que esta sea una solución correcta.
Devuelve None si una de las Opciones de la Lista es None, de lo contrario devuelve la Opción de 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
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-05 22:56:22