Konvertieren eine Liste der Optionen, um eine Option der Liste mit Scalaz
Frage
Ich möchte ein List[Option[T]]
in eine Option[List[T]]
verwandeln. Der Signaturtyp der Funktion
def lo2ol[T](lo: List[Option[T]]): Option[List[T]]
Das erwartete Verhalten ist eine Liste zuzuordnen, die nur Some
s in eine Some
enthält eine Liste der Elemente im Inneren der Elemente Some
des enthält. Auf der anderen Seite, wenn die Eingabeliste mindestens eine None
hat, ist das erwartete Verhalten None
nur zurückzukehren. Zum Beispiel:
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())
Eine beispielhafte Implementierung, ohne scalaz, wäre:
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]];
}}}
Ich erinnere mich, dass irgendwo ein ähnliches Beispiel, aber unter Verwendung von Scalaz den Code zu vereinfachen. Wie würde es aussehen?
Eine etwas knappere Version, mit Scala2.8 PartialFunction.condOpt
, aber immer noch ohne 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
}
}}
Lösung
Es gibt eine Funktion, die eine List[Option[A]]
in eine Option[List[A]]
in Scalaz dreht. Es ist sequence
. Um None
im Falle irgendeines der Elemente sind None
und ein Some[List[A]]
zu bekommen, falls alle Elemente sind Some
, können Sie dies nur tun:
import scalaz.syntax.traverse._
import scalaz.std.list._
import scalaz.std.option._
lo.sequence
Diese Methode tatsächlich dreht F[G[A]
in G[F[A]]
gegeben, dass es eine Implementierung von Traverse[F]
existiert, und von Applicative[G]
(Option
und List
passieren beide erfüllen und werden durch diese Einfuhren zur Verfügung gestellt).
Die Semantik Applicative[Option]
ist so, dass, wenn eine der Elemente eines List
von Option
s None
ist, dann ist die sequence
None
als auch sein wird. Wenn Sie eine Liste aller Some
Werte unabhängig davon erhalten möchten, ob andere Werte sind None
, Sie können dies tun:
lo flatMap (_.toList)
Sie können, dass für jede Monad
verallgemeinern, dass auch eine Monoid
bildet (List
geschieht eine davon zu sein):
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))
Andere Tipps
Aus irgendeinem Grund, den Sie nicht mögen
if (lo.exists(_ isEmpty)) None else Some(lo.map(_.get))
? Das ist wahrscheinlich die kürzeste in Scala ohne Scalaz.
Während die Applicative[Option]
in Scalaz das falsche Verhalten muss direkt Verwendung MA#sequence
, können Sie ableiten auch eine Applicative
von einem Monoid
. Dies wird bequem mit MA#foldMapDefault
oder MA#collapse
.
In diesem Fall verwenden wir eine Monoid[Option[List[Int]]
. Wir führen eine innere erste Karte (MA#∘∘
) die einzelnen Int
s in List
s von einem Element zu wickeln.
(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]]
Abstrahieren von List
einen Behälter mit Instanzen für Traverse
, Pointed
und 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]]
Leider versuchen, diesen Code zu kompilieren derzeit entweder Trigger # 2741 oder sendet der Compiler in eine Endlosschleife.
UPDATE
Um zu vermeiden, zweimal durchlaufen die Liste, ich gebrauchte foldMapDefault
haben:
(List(some(1), none[Int], some(2)) foldMapDefault (_ ∘ ((_: Int).pure[List])))
Diese Antwort wurde auf der ursprünglichen Anforderung aus, dass eine leere Liste oder eine Liste, die nur None
s, sollte eine None
zurück. Im übrigen würde dies am besten durch die Art Option[scalaz.NonEmptyList]
modelliert -. NonEmptyList
garantiert mindestens ein Element
Wenn Sie nur die eine List[Int]
wollen, gibt es viele einfachere Möglichkeiten, in anderen Antworten gegeben. Zwei direkte Wege, die nicht erwähnt haben:
list collect { case Some(x) => x }
list flatten
Das ist für mich gearbeitet. Ich hoffe, dies ist eine richtige Lösung.
Es gibt keine, wenn eine der Optionen in der Liste keine, sonst gibt es eine Option der 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
}
}
}
Starten Scala 2.13
, und die Zugabe des Option::unless
Builder die Standardbibliothek , eine Variante zu Rex Kerrs Antwort wäre:
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
oder, wenn die Leistung auf dem Spiel steht (um zu vermeiden, flatten
die implizite Konvertierung von Option
zu List
):
Option.unless(list contains None)(list.map(_.get))