Typ Mismatch auf Scala Für Auffassungs
-
12-10-2019 - |
Frage
Warum diese Konstruktion Ursache eines Type Mismatch Fehler in Scala?
for (first <- Some(1); second <- List(1,2,3)) yield (first,second)
<console>:6: error: type mismatch;
found : List[(Int, Int)]
required: Option[?]
for (first <- Some(1); second <- List(1,2,3)) yield (first,second)
Wenn ich den Teil mit der List-Schalter kompiliert es fein:
for (first <- List(1,2,3); second <- Some(1)) yield (first,second)
res41: List[(Int, Int)] = List((1,1), (2,1), (3,1))
Das funktioniert auch gut:
for (first <- Some(1); second <- Some(2)) yield (first,second)
Lösung
Für Comprehensions werden in Aufrufe an die map
oder flatMap
Verfahren umgewandelt. Zum Beispiel dieses:
for(x <- List(1) ; y <- List(1,2,3)) yield (x,y)
wird, dass:
List(1).flatMap(x => List(1,2,3).map(y => (x,y)))
Daher ist der erste Schleifenwert (in diesem Fall List(1)
) den flatMap
Methodenaufruf empfängt. Da flatMap
auf einem List
weiteren List
zurückkehrt, wird das Ergebnis der für das Verständnis natürlich ein List
sein. (Das war mir neu: Für Comprehensions nicht immer in Strömen führen, auch nicht unbedingt in Seq
s.)
Nehmen Sie nun, einen Blick auf, wie flatMap
in Option
deklariert wird:
def flatMap [B] (f: (A) ⇒ Option[B]) : Option[B]
Beachten Sie dies. Mal sehen, wie die fehlerhafte für das Verständnis (die mit Some(1)
) auf eine Folge von Karte Anrufe umgewandelt wird:
Some(1).flatMap(x => List(1,2,3).map(y => (x, y)))
Nun, es ist leicht zu sehen, dass der Parameter des flatMap
Anrufs ist etwas, dass die Renditen eines List
, aber keine Option
, je nach Bedarf.
Um die Sache zu beheben, können Sie Folgendes tun:
for(x <- Some(1).toSeq ; y <- List(1,2,3)) yield (x, y)
Das compiles just fine. Es ist erwähnenswert, dass Option
Bemerken ist kein Subtyp von Seq
, wie oft angenommen wird.
Andere Tipps
Ein einfacher Tipp zu erinnern, für Comprehensions wird versuchen, die Art der Sammlung des ersten Generators zurückzukehren, Option [Int] in diesem Fall. Also, wenn Sie beginnen mit Manche (1) sollten Sie ein Ergebnis der Option [T] erwarten.
Wenn Sie ein Ergebnis wollen Liste Typ, sollten Sie mit einem Listengenerator starten.
Warum diese Einschränkung haben, und Sie nicht immer davon ausgehen, werden irgendeine Art von Sequenz wollen? Sie können eine Situation, wo es sinnvoll, Rückkehr Option
macht. Vielleicht haben Sie eine Option[Int]
, dass Sie mit etwas verbinden wollen eine Option[List[Int]]
, sagen mit der folgenden Funktion zu erhalten: (i:Int) => if (i > 0) List.range(0, i) else None
; Sie könnten dann diese schreiben und keine bekommen, wenn die Dinge nicht „sinnvoll“:
val f = (i:Int) => if (i > 0) Some(List.range(0, i)) else None
for (i <- Some(5); j <- f(i)) yield j
// returns: Option[List[Int]] = Some(List(0, 1, 2, 3, 4))
for (i <- None; j <- f(i)) yield j
// returns: Option[List[Int]] = None
for (i <- Some(-3); j <- f(i)) yield j
// returns: Option[List[Int]] = None
Wie für Comprehensions ist im allgemeinen Fall erweitert in der Tat ein ziemlich allgemeiner Mechanismus ein Objekt vom Typ M[T]
mit einer Funktion (T) => M[U]
zu kombinieren, um ein Objekt vom Typ M[U]
zu bekommen. In Ihrem Beispiel kann M Option oder Liste sein. Im Allgemeinen hat es die gleiche Art M
sein. So können Sie nicht Option mit Liste kombinieren. Beispiele für andere Dinge, die M
, Blick auf sein können Subklassen dieses Merkmal .
Warum hat List[T]
mit (T) => Option[T]
Arbeit kombiniert, obwohl, wenn Sie mit der Liste gestartet? In diesem Fall nutzt die Bibliothek eine allgemeinere Art, wo es Sinn macht. So können Sie die Liste mit Travers kombinieren können, und es gibt eine implizite Konvertierung von Option zu Travers.
Unter dem Strich ist dies: darüber nachdenken, was geben Sie den Ausdruck einfügen möchten mit diesem Typ wie der erste Generator zurückzukehren und zu starten. Wickeln Sie es in dieser Art, falls erforderlich.
Es hat wahrscheinlich etwas mit der Option zu tun, nicht ein Iterable zu sein. Die implizite Option.option2Iterable
wird den Fall behandeln, in denen Compiler zweite erwartet ein Iterable zu sein. Ich gehe davon aus, dass die Compiler Magie unterscheidet je nach Art der Schleifenvariable.
Ich fand immer dies hilfreich:
scala> val foo: Option[Seq[Int]] = Some(Seq(1, 2, 3, 4, 5))
foo: Option[Seq[Int]] = Some(List(1, 2, 3, 4, 5))
scala> foo.flatten
<console>:13: error: Cannot prove that Seq[Int] <:< Option[B].
foo.flatten
^
scala> val bar: Seq[Seq[Int]] = Seq(Seq(1, 2, 3, 4, 5))
bar: Seq[Seq[Int]] = List(List(1, 2, 3, 4, 5))
scala> bar.flatten
res1: Seq[Int] = List(1, 2, 3, 4, 5)
scala> foo.toSeq.flatten
res2: Seq[Int] = List(1, 2, 3, 4, 5)