Warum verursacht diese Konstruktion in Scala einen Typ-Mismatch-Fehler?
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 das Einige mit der Liste wechsle, wird es gut kompiliert:
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)
scala
for-loop
type-mismatch
for-comprehension
scala-option
Felipe Kamakura
quelle
quelle
Antworten:
Zum Verständnis werden in Aufrufe der Methode
map
oder umgewandeltflatMap
. Zum Beispiel dieses:for(x <- List(1) ; y <- List(1,2,3)) yield (x,y)
wird das:
List(1).flatMap(x => List(1,2,3).map(y => (x,y)))
Daher
List(1)
erhält der erste Schleifenwert (in diesem Fall ) denflatMap
Methodenaufruf. DaflatMap
bei einerList
Rückkehr eine andere istList
, wird das Ergebnis des Verständnisses natürlich a seinList
. (Das war neu für mich: Zum Verständnis führen nicht immer Streams, nicht einmal unbedingt zuSeq
s.)Schauen Sie sich nun an, wie
flatMap
deklariert wird inOption
:def flatMap [B] (f: (A) ⇒ Option[B]) : Option[B]
Denken Sie daran. Mal sehen, wie das fehlerhafte Verständnis (das mit
Some(1)
) in eine Folge von Kartenaufrufen umgewandelt wird:Some(1).flatMap(x => List(1,2,3).map(y => (x, y)))
Jetzt ist leicht zu erkennen, dass der Parameter des
flatMap
Aufrufs bei Bedarf einList
, aber kein a zurückgibtOption
.Um das Problem zu beheben, können Sie Folgendes tun:
for(x <- Some(1).toSeq ; y <- List(1,2,3)) yield (x, y)
Das passt gut zusammen. Es ist erwähnenswert, dass dies
Option
kein Subtyp von istSeq
, wie oft angenommen wird.quelle
Ein leicht zu merkender Tipp, zum Verständnis wird versucht, den Typ der Sammlung des ersten Generators, in diesem Fall Option [Int], zurückzugeben. Wenn Sie also mit Some (1) beginnen , sollten Sie ein Ergebnis von Option [T] erwarten.
Wenn Sie ein Ergebnis vom Listentyp wünschen , sollten Sie mit einem Listengenerator beginnen.
Warum diese Einschränkung haben und nicht davon ausgehen, dass Sie immer eine Sequenz wollen? Sie können eine Situation haben, in der es sinnvoll ist, zurückzukehren
Option
. Vielleicht haben Sie eineOption[Int]
, die Sie mit etwas kombinieren möchten, um eine zu erhaltenOption[List[Int]]
, sagen wir mit der folgenden Funktion :(i:Int) => if (i > 0) List.range(0, i) else None
; Sie könnten dies dann schreiben und None erhalten, wenn die Dinge keinen "Sinn ergeben":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 das Verständnis im allgemeinen Fall erweitert wird, ist in der Tat ein ziemlich allgemeiner Mechanismus, um ein Objekt vom Typ
M[T]
mit einer Funktion(T) => M[U]
zu kombinieren , um ein Objekt vom Typ zu erhaltenM[U]
. In Ihrem Beispiel kann M Option oder Liste sein. Im Allgemeinen muss es der gleiche Typ seinM
. Sie können Option also nicht mit List kombinieren. Beispiele für andere mögliche Dinge finden SieM
in Unterklassen dieses Merkmals .Warum hat das Kombinieren
List[T]
mit der(T) => Option[T]
Arbeit funktioniert, als Sie mit der Liste begonnen haben? In diesem Fall verwendet die Bibliothek einen allgemeineren Typ, wenn dies sinnvoll ist. Sie können also List mit Traversable kombinieren und es gibt eine implizite Konvertierung von Option zu Traversable.Das Fazit lautet: Überlegen Sie, welchen Typ der Ausdruck zurückgeben soll, und beginnen Sie mit diesem Typ als erstem Generator. Wickeln Sie es bei Bedarf in diesen Typ ein.
quelle
for
Syntax für diese Art von Funktor / monadischem Desugaring zu verwenden. Warum nicht anders benannte Methoden für die Zuordnung von Funktoren / Monadenfmap
usw. und Reservesyntax verwendenfor
, um ein äußerst einfaches Verhalten zu erzielen, das den Erwartungen entspricht, die von praktisch jeder anderen gängigen Programmiersprache ausgehen?Es hat wahrscheinlich etwas damit zu tun, dass Option kein Iterable ist. Das Implizite
Option.option2Iterable
behandelt den Fall, in dem der Compiler erwartet, dass die Sekunde eine Iterable ist. Ich erwarte, dass die Compiler-Magie je nach Typ der Schleifenvariablen unterschiedlich ist.quelle
Ich fand das immer 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)
quelle