2010-04-02 21 views
24

Quiero transformar un List[Option[T]] en un Option[List[T]]. El tipo de firma de la función esConvertir una lista de opciones a una opción de lista usando Scalaz

def lo2ol[T](lo: List[Option[T]]): Option[List[T]] 

El comportamiento esperado es mapear una lista que contiene sólo Some s en un Some que contienen una lista de los elementos dentro de los elementos Some 's. 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, y 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 alguna parte un ejemplo similar, pero usando Scalaz para simplificar el código. ¿Cómo se vería?


Una versión ligeramente más sucinta, 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 
    } 
}} 

Respuesta

20

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 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 resultan satisfacer ambos y son proporcionados 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 habrá None también. Si desea obtener una lista de todos los Some valores independientemente de si otros valores son None, usted puede hacer esto:

lo flatMap (_.toList) 

Usted 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)) 
+1

Pero si solo desea los valores 'Some' de' List [Option [A]] ', ya no necesita' Option'. Tendrás una lista vacía o una lista no vacía de 'A'. – Apocalisp

+0

En realidad, quiero obtener 'None' en caso de que alguno de los elementos sea' None', entonces 'sequence' es exactamente lo que solicité. Trataré de editar la pregunta para aclarar el requisito. –

+0

No puedo hacer que 'lo.sequence' funcione. Usando scala-2.8.0.Beta1 y scalaz-core_2.8.0.Beta1-5.0.1-SNAPSHOT.jar, si ingreso 'def lo2ol [T] (lo: List [Option [T]]): Option [List [ T]] = lo.sequence', obtengo ': 10: error: expansión implícita divergente para el tipo scalaz.Applicative [N]' –

15

Por alguna razón que no le gusta

if (lo.exists(_ isEmpty)) None else Some(lo.map(_.get)) 

? Eso es probablemente el más corto en Scala sin Scalaz.

+2

No está mal, pero va más de la lista dos veces. – Apocalisp

+3

@Apocalisp: De hecho, pero eso es casi siempre más barato que hacer la mitad de una nueva lista antes de descartarlo todo y devolver 'None'. –

+1

Tiendo a no gustarme 'Option.get', pero parece estar bien aquí. –

2

Mientras que el Applicative[Option] en Scalaz tiene el comportamiento incorrecto utilizar directamente MA#sequence, también puede derivar una 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 el Int individual 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 actualmente ya sea desencadena #2741 o envía el compilador en un bucle infinito.

ACTUALIZACIÓN Para evitar que atraviesa la lista dos veces, debería haber utilizado foldMapDefault:

(List(some(1), none[Int], some(2)) foldMapDefault (_ ∘ ((_: Int).pure[List]))) 

Esta respuesta se basó en la solicitud original que una lista vacía, o una lista que contiene sólo None s, debe devuelva un None. Por cierto, esto sería mejor modelado por el tipo Option[scalaz.NonEmptyList] - NonEmptyList garantiza al menos un elemento.

Si solo desea el a List[Int], hay muchas maneras más fáciles, dadas en otras respuestas. Dos formas directas que no se han mencionado:

list collect { case Some(x) => x } 
list flatten 
+0

El caso de uso real detrás de esta pregunta se cumple perfectamente con el método 'sequence 'en la respuesta de Apocalisp, incluido el resultado cuando se le da una lista vacía (lo que puede suceder en dicho caso de uso). Pero gracias por mencionar el tipo de NEL, parece práctico. –

+0

@retronym: Rafael escribió "si la lista de entrada tiene al menos una None, el comportamiento esperado es simplemente devolver None". Donde como escribió "una lista que contiene solo Nones, debe devolver una Ninguno". Eso es muy diferente. "apilar la lista" no hace lo que Rafael realmente pidió. –

+0

Malo, interpreté mal la pregunta. 'MA # foldMapDefault' y' MA # sequence' usan 'Traverse [List]', pero usan un Funcionador Aplicativo diferente. – retronym

0

Esto funcionó para mí. Espero que esta sea una solución correcta.

Retorna Ninguno 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 

    } 
    } 

} 
Cuestiones relacionadas