Question

Pourquoi cette construction provoquer une erreur Incohérence de type à 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)

Si je passe la partie avec la liste, il compile bien:

for (first <- List(1,2,3); second <- Some(1)) yield (first,second)
res41: List[(Int, Int)] = List((1,1), (2,1), (3,1))

Cela fonctionne aussi bien:

for (first <- Some(1); second <- Some(2)) yield (first,second)
Était-ce utile?

La solution

Pour compréhensions sont convertis en appels à la méthode de map ou flatMap. Par exemple celui-ci:

for(x <- List(1) ; y <- List(1,2,3)) yield (x,y)

devient que:

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

Par conséquent, la première valeur de boucle (dans ce cas, List(1)) reçoit l'appel de méthode de flatMap. Depuis flatMap sur un List retourne une autre List, le résultat de la compréhension pour sera bien sûr un être List. (C'était nouveau pour moi: Pour compréhensions ne donnent pas toujours des cours d'eau, même pas nécessairement Seqs.)

Maintenant, jetez un oeil à la façon dont flatMap est déclaré dans Option:

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

Gardez cela à l'esprit. Voyons voir comment l'erreur de compréhension (celle avec Some(1)) est convertie en une séquence d'appels de carte:

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

Maintenant, il est facile de voir que le paramètre de l'appel flatMap est quelque chose qui renvoie une List, mais pas un Option, au besoin.

Afin de corriger la chose, vous pouvez faire ce qui suit:

for(x <- Some(1).toSeq ; y <- List(1,2,3)) yield (x, y)

compilant très bien. Il est intéressant de noter que Option n'est pas un sous-type de Seq, comme on le suppose souvent.

Autres conseils

Une astuce simple à retenir, pour compréhensions va essayer de retourner le type de la collection du premier générateur, l'option [Int] dans ce cas. Donc, si vous commencez avec Certains (1) vous devriez attendre à un résultat de l'option [T].

Si vous voulez un résultat de de type, vous devez commencer par un générateur de liste.

Pourquoi cette restriction et ne pas présumer que vous voulez toujours une sorte de séquence? Vous pouvez avoir une situation où il est logique de Option de retour. Peut-être vous avez un Option[Int] que vous voulez combiner avec quelque chose pour obtenir un Option[List[Int]], disons avec la fonction suivante: (i:Int) => if (i > 0) List.range(0, i) else None; vous pouvez alors écrire cela et aucune quand les choses ne sont pas « du sens »:

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

Comment pour compréhensions sont développés dans le cas général sont en fait un mécanisme assez général de combiner un objet de type M[T] avec une (T) => M[U] fonction pour obtenir un objet de type M[U]. Dans votre exemple, M peut être l'option Liste. En général, il doit être le même type M. vous ne pouvez donc pas combiner avec l'option Liste. Pour des exemples d'autres choses qui peuvent être M, consultez sous-classes de ce trait .

Pourquoi la combinaison List[T] avec le travail de (T) => Option[T] mais quand vous avez commencé avec la liste? Dans ce cas, la bibliothèque utilise un type plus général où il est logique. Ainsi, vous pouvez combiner liste avec Traversable et il y a une conversion implicite de l'option à Traversable.

La ligne de fond est la suivante: pensez à ce type que vous voulez l'expression pour revenir et commencer par ce type comme le premier générateur. Enveloppez-le dans ce type si nécessaire.

Il a probablement quelque chose à voir avec l'option de ne pas être un Iterable. L'implicite Option.option2Iterable va gérer le cas où le compilateur attend deuxième être un Iterable. Je pense que la magie du compilateur est différent en fonction du type de la variable de la boucle.

J'ai toujours trouvé cela utile:

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)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top