Convertir una lista de opciones en una opción de lista usando Scalaz
option (4)
Esto funcionó para mí. Espero que esta sea una solución correcta.
Devuelve Ninguno si una de las Opciones en la Lista es Ninguna, 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
}
}
}
Quiero transformar una List[Option[T]]
en una 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 asignar una lista que contenga solo Some
s en un Some
contenga una lista de los elementos dentro de los elementos de 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 un ejemplo similar en alguna parte, pero usando Scalaz para simplificar el código. ¿Cómo se vería?
Una versión ligeramente más concisa, utilizando 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
}
}}
Hay una función que convierte una List[Option[A]]
en una Option[List[A]]
en Scalaz. Es sequence
Para obtener None
en caso de que alguno de los elementos sea None
y un Some[List[A]]
en caso de que todos los elementos sean Some
, solo 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]
(la Option
y la List
cumplen ambos requisitos y son proporcionadas por esas importaciones ).
La semántica de Applicative[Option]
es tal que si alguno de los elementos de una List
de Option
es None
, entonces la sequence
también será None
. Si desea obtener una lista de todos los valores de Some
, independientemente de si otros valores son None
, puede hacer esto:
lo flatMap (_.toList)
Puede generalizar que para cualquier Monad
que también forme un Monoid
(la List
es una de estas):
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))
Por alguna razón no te gusta
if (lo.exists(_ isEmpty)) None else Some(lo.map(_.get))
? Ese es probablemente el más corto en Scala sin Scalaz.
Si bien la Applicative[Option]
aplicativa en Scalaz tiene el comportamiento incorrecto para usar directamente la 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
individuales en la List
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]]
Resumen de la List
a cualquier contenedor con instancias de 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, al intentar compilar este código actualmente, se activa el #2741 o se 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 contenga solo None
, debe devolver una None
. Por cierto, esto sería mejor modelado por el tipo Option[scalaz.NonEmptyList]
- NonEmptyList
garantiza al menos un elemento.
Si solo desea una List[Int]
, hay muchas maneras más fáciles, dadas en otras respuestas. Dos formas directas que no han sido mencionadas:
list collect { case Some(x) => x }
list flatten