wake-up-neo.com

Typabweichung bei Scala zum Verständnis

Warum verursacht diese Konstruktion einen Typenkonfliktfehler 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 einige mit der Liste umschalte, 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)
72
Felipe Kamakura

Für Verständnis werden in Aufrufe der map- oder flatMap-Methode konvertiert. 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 erhält der erste Schleifenwert (in diesem Fall List(1)) den Aufruf der flatMap-Methode. Da flatMap auf einer List eine andere List zurückgibt, ist das Ergebnis des Verständnisses natürlich eine List. (Das war neu für mich: Für Verständnis führen nicht immer Streams, nicht notwendigerweise auch Seqs.)

Sehen Sie sich nun an, wie flatMap in Option deklariert ist:

def flatMap [B] (f: (A) ⇒ Option[B]) : Option[B]

Behalte dies im Kopf. Mal sehen, wie das für das Verstehen irrtümliche (das mit Some(1)) falsch in eine Folge von Kartenaufrufen umgewandelt wird:

Some(1).flatMap(x => List(1,2,3).map(y => (x, y)))

Nun ist es leicht zu sehen, dass der Parameter des Aufrufs flatMap etwas ist, das eine List zurückgibt, jedoch nicht eine Option, je nach Bedarf.

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 Option kein Subtyp von Seq ist, wie oft angenommen wird.

110
Madoc

Ein einfacher Tipp zum Erinnern: forompartments versucht, den Typ der Sammlung des ersten Generators zurückzugeben, in diesem Fall Option [Int]. Wenn Sie also mit Some (1) beginnen, sollten Sie ein Ergebnis von Option [T] erwarten. 

Wenn Sie ein Ergebnis vom Typ List wünschen, sollten Sie mit einem Listengenerator beginnen.

Warum haben Sie diese Einschränkung und gehen nicht davon aus, dass Sie immer eine Art Sequenz wünschen? Es kann vorkommen, dass es sinnvoll ist, Option zurückzugeben. Vielleicht haben Sie einen Option[Int], den Sie mit etwas kombinieren möchten, um einen Option[List[Int]] zu erhalten, sagen Sie mit der folgenden Funktion: (i:Int) => if (i > 0) List.range(0, i) else None; Sie könnten dann dies 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 für das Verständnis im allgemeinen Fall erweitert wird, ist in der Tat ein recht allgemeiner Mechanismus, um ein Objekt vom Typ M[T] mit einer Funktion (T) => M[U] zu kombinieren, um ein Objekt vom Typ M[U] zu erhalten. In Ihrem Beispiel kann M Option oder List sein. Im Allgemeinen muss es sich um denselben Typ M handeln. Sie können also Option nicht mit List kombinieren. Beispiele für andere Dinge, die M sein können, finden Sie in Unterklassen dieses Merkmals .

Warum funktionierte das Kombinieren von List[T] mit (T) => Option[T] zu Beginn der Liste? In diesem Fall verwendet die Bibliothek einen allgemeineren Typ, wenn dies sinnvoll ist. Sie können also List mit Traversable kombinieren und es erfolgt eine implizite Konvertierung von Option in Traversable.

Die letzte Zeile lautet: Überlegen Sie, welchen Typ der Ausdruck zurückgeben soll, und beginnen Sie mit diesem Typ als erstem Generator. Wickeln Sie es gegebenenfalls in diesen Typ ein. 

30
huynhjl

Es hat wahrscheinlich etwas damit zu tun, dass Option keine Iterable ist. Der implizite Option.option2Iterable wird den Fall behandeln, in dem der Compiler erwartet, dass second eine Iterable ist. Ich gehe davon aus, dass sich der Compiler-Zauber je nach Typ der Schleifenvariablen unterscheidet.

4
sblundy

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