Convertire un elenco di opzioni ad un opzione di elenco utilizzando Scalaz
Domanda
Voglio trasformare un List[Option[T]]
in un Option[List[T]]
. Il tipo di firma della funzione è
def lo2ol[T](lo: List[Option[T]]): Option[List[T]]
Il comportamento previsto è di mappare una lista che contiene solo Some
s in un Some
contenente un elenco degli elementi all'interno del elementi Some
del. D'altra parte, se la lista di ingresso ha almeno un None
, il comportamento previsto è quello di tornare solo None
. Ad esempio:
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 esempio di implementazione, senza scalaz, potrebbe essere:
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]];
}}}
Ricordo di aver visto da qualche parte un esempio simile, ma utilizzando Scalaz per semplificare il codice. Come apparirebbe come?
Una versione leggermente più succinto, utilizzando Scala2.8 PartialFunction.condOpt
, ma ancora senza 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
}
}}
Soluzione
C'è una funzione che trasforma un List[Option[A]]
in un Option[List[A]]
in Scalaz. E 'sequence
. Per ottenere None
in caso di qualsiasi degli elementi sono None
e Some[List[A]]
nel caso in cui tutti gli elementi sono Some
, si può solo fare questo:
import scalaz.syntax.traverse._
import scalaz.std.list._
import scalaz.std.option._
lo.sequence
Questo metodo trasforma effettivamente F[G[A]
in G[F[A]]
dato che esiste un'implementazione di Traverse[F]
, e di Applicative[G]
(Option
e List
capita di soddisfare entrambi e sono forniti da tali importazioni).
La semantica Applicative[Option]
sono tali che se uno degli elementi di una List
di Option
s sono None
, allora il sequence
sarà None
pure. Se si desidera ottenere un elenco di tutti i valori Some
indipendentemente dal fatto che tutti gli altri valori sono None
, si può fare questo:
lo flatMap (_.toList)
È possibile generalizzare che per ogni Monad
che costituisce anche una Monoid
(List
sembra essere uno di questi):
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))
Altri suggerimenti
Per qualche motivo non ti piace
if (lo.exists(_ isEmpty)) None else Some(lo.map(_.get))
? Questa è probabilmente la più breve a Scala, senza Scalaz.
Mentre il Applicative[Option]
nel Scalaz ha il comportamento sbagliato direttamente uso MA#sequence
, si può anche derivare una Applicative
da un Monoid
. Ciò è reso conveniente con MA#foldMapDefault
o MA#collapse
.
In questo caso, si usa una Monoid[Option[List[Int]]
. Per prima cosa effettuare una mappa interna (MA#∘∘
) per avvolgere i singoli Int
s in List
s di 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]]
Astraendo da List
a qualsiasi contenitore con istanze per Traverse
, Pointed
e 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]]
Purtroppo, cercando di compilare il codice attualmente sia trigger # 2741 o invia il compilatore in un ciclo infinito.
Aggiorna
Per evitare l'attraversamento della lista due volte, dovrei avere foldMapDefault
usato:
(List(some(1), none[Int], some(2)) foldMapDefault (_ ∘ ((_: Int).pure[List])))
Questa risposta è stata basata sulla richiesta originale che una lista vuota, o un elenco contenente solo None
s, dovrebbero restituire un None
. Per inciso, questo sarebbe meglio modellata dal tipo Option[scalaz.NonEmptyList]
-. Garanzie NonEmptyList
almeno un elemento
Se si desidera solo l'un List[Int]
, ci sono molti modi più facili, dato in altre risposte. Due modi diretti che non sono stati menzionati:
list collect { case Some(x) => x }
list flatten
Questo ha funzionato per me. Spero che questa è una soluzione corretta.
Si restituisce None se una delle opzioni nell'elenco vale None, altrimenti restituisce Opzione di 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
}
}
}
Scala 2.13
, e l'aggiunta di Option::unless
costruttore al libreria standard , una variante di Rex Kerr risposta sarebbe:
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, se le prestazioni sono in gioco (al fine di evitare di conversione implicita di flatten
da Option
a List
):
Option.unless(list contains None)(list.map(_.get))