Question

Je veux transformer un List[Option[T]] en Option[List[T]]. Le type de signature de la fonction est

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

Le comportement attendu est de cartographier une liste qui contient seulement Somes dans un Some contenant une liste des éléments à l'intérieur des éléments Some pour. D'autre part, si la liste d'entrée a au moins un None, le comportement attendu est de retour juste None. Par exemple:

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 exemple d'implémentation, sans scalaz, serait:

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]]; 
}}}

Je me souviens avoir vu quelque part un exemple similaire, mais en utilisant Scalaz pour simplifier le code. Comment serait-il ressembler?


Une version légèrement plus succincte, à l'aide de PartialFunction.condOpt Scala2.8, mais toujours sans 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
  }
}}
Était-ce utile?

La solution

Il y a une fonction qui transforme un List[Option[A]] en un Option[List[A]] en Scalaz. Il est sequence. Pour obtenir None au cas où des éléments sont None et un Some[List[A]] dans le cas où tous les éléments sont Some, vous pouvez simplement faire ceci:

import scalaz.syntax.traverse._
import scalaz.std.list._     
import scalaz.std.option._

lo.sequence

Cette méthode se fait F[G[A] en G[F[A]] étant donné qu'il existe une mise en œuvre de Traverse[F] et de Applicative[G] (Option et List arriver à satisfaire à la fois et sont fournis par les importations).

La sémantique de Applicative[Option] sont telles que si l'un des éléments d'un List de Options sont None, le sequence sera None aussi. Si vous souhaitez obtenir une liste de toutes les valeurs de Some indépendamment du fait que toutes les autres valeurs sont None, vous pouvez faire ceci:

lo flatMap (_.toList)

Vous pouvez généraliser que pour tout Monad qui forme aussi un Monoid (List se trouve être l'un d'entre eux):

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))

Autres conseils

Pour une raison quelconque vous n'aimez pas

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

? C'est probablement le plus court à Scala sans Scalaz.

Alors que le Applicative[Option] dans Scalaz a le mauvais comportement à utiliser directement MA#sequence, vous pouvez également tirer un Applicative d'un Monoid. Ceci est rendu pratique avec MA#foldMapDefault ou MA#collapse.

Dans ce cas, nous utilisons un Monoid[Option[List[Int]]. Nous effectuons une première carte interne (MA#∘∘) pour envelopper les Ints individuels dans Lists d'un élément.

(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]]

Abstraction faite de List à un conteneur avec les instances de Traverse, Pointed et 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]]

Malheureusement, en essayant de compiler ce code actuellement, soit déclenche # 2741 ou envoie le compilateur dans une boucle infinie.

UPDATE Pour éviter de traverser la liste deux fois, je devrais avoir utilisé foldMapDefault:

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

Cette réponse a été basée sur la demande initiale qu'une liste vide ou une liste ne contenant que Nones, devraient retourner un None. Soit dit en passant, ce serait mieux modélisé par le type Option[scalaz.NonEmptyList] -. Garantit NonEmptyList au moins un élément

Si vous voulez juste l'un List[Int], il y a beaucoup de façons plus faciles, données dans d'autres réponses. Deux moyens directs qui ne sont pas mentionnés:

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

Cela a fonctionné pour moi. J'espère que cela est une bonne solution.

Il retourne Aucun si l'une des options de la liste est Aucun, sinon il retourne l'option de la liste [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

    }
  }

}
scroll top