Type Mismatch sur Scala For Comprehension

Pourquoi cette construction provoque-t-elle une erreur de type incompatibilité dans Scala?

for (first <- Some(1); second <- List(1,2,3)) yield (first,second) :6: error: type mismatch; found : List[(Int, Int)] required: Option[?] for (first <- Some(1); second <- List(1,2,3)) yield (first,second) 

Si je change de Some avec la liste, il comstack 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 très bien:

 for (first <- Some(1); second <- Some(2)) yield (first,second) 

Pour que les compréhensions soient converties en appels à la méthode 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) ) recevra l' flatMap méthode flatMap . Puisque flatMap sur une List renvoie une autre List , le résultat de la compréhension sera bien sûr une List . (Cela était nouveau pour moi: car les compréhensions ne donnent pas toujours lieu à des stream, même pas nécessairement dans les Seq .)

Maintenant, regardez comment flatMap est déclaré dans Option :

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

Garde ça en tête. Voyons 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 une Option , comme requirejs.

Pour résoudre le problème, vous pouvez effectuer les opérations suivantes:

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

Cela comstack très bien. Il est à noter que Option n'est pas un sous-type de Seq , comme on le suppose souvent.

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

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

Pourquoi avoir cette ressortingction et ne pas supposer que vous voulez toujours une sorte de séquence? Vous pouvez avoir une situation où il est logique de retourner Option . Peut-être que vous avez une Option[Int] que vous voulez combiner avec quelque chose pour obtenir une Option[List[Int]] , par exemple avec la fonction suivante: (i:Int) => if (i > 0) List.range(0, i) else None ; vous pourriez alors écrire ceci et obtenir None quand les choses n’ont pas de 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 

La manière dont les compréhensions sont développées dans le cas général est en fait un mécanisme assez général pour combiner un object de type M[T] avec une fonction (T) => M[U] pour obtenir un object de type M[U] . Dans votre exemple, M peut être Option ou Liste. En général, il doit être du même type M Donc, vous ne pouvez pas combiner Option avec List. Pour des exemples d'autres choses qui peuvent être M , regardez les sous-classes de ce trait .

Pourquoi la combinaison de List[T] avec (T) => Option[T] fonctionné quand vous avez commencé avec la liste? Dans ce cas, la bibliothèque utilise un type plus général où cela a du sens. Vous pouvez donc combiner List avec Traversable et il existe une conversion implicite d'Option en Traversable.

La ligne du bas est la suivante: pensez à quel type vous voulez que l'expression retourne et commencez avec ce type comme premier générateur. Enveloppez-le dans ce type si nécessaire.

Cela a probablement quelque chose à voir avec l’option ne pas être une itération. Option.option2Iterable implicite Option.option2Iterable le cas où le compilateur s’attend à ce que le second soit une itération. Je pense que la magie du compilateur est différente selon le type de la variable de 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 :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)