Impossible d’utiliser pour la compréhension de la carte dans Future

J’ai ce problème que je dois contourner à chaque fois. Je ne peux pas cartographier quelque chose qui est contenu dans un futur en utilisant un pour la compréhension.

Exemple:

import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future val f = Future( List("A", "B", "C") ) for { list <- f e  1) 

Cela me donne l’erreur:

  error: type mismatch; found : List[(Ssortingng, Int)] required: scala.concurrent.Future[?] e <- list ^ 

Mais si je fais cela, ça marche bien:

 f.map( _.map( (_ -> 1) ) ) 

Ne devrais-je pas pouvoir faire cela en utilisant un pour la compréhension, est-ce la raison pour laquelle cela fonctionne dans mon autre exemple que je ne mets pas à plat? J’utilise Scala 2.10.0.

Eh bien, lorsque vous avez plusieurs générateurs dans un seul pour la compréhension, vous aplatissez le type résultant. C’est-à-dire qu’au lieu d’obtenir une List[List[T]] , vous obtenez une List[T] :

 scala> val list = List(1, 2, 3) list: List[Int] = List(1, 2, 3) scala> for (a <- list) yield for (b <- list) yield (a, b) res0: List[List[(Int, Int)]] = List(List((1,1), (1,2), (1,3)), List((2,1 ), (2,2), (2,3)), List((3,1), (3,2), (3,3))) scala> for (a <- list; b <- list) yield (a, b) res1: List[(Int, Int)] = List((1,1), (1,2), (1,3), (2,1), (2,2), (2,3), (3,1), (3,2), (3,3)) 

Maintenant, comment tu aplanir un Future[List[T]] ? Ce ne peut pas être un Future[T] , car vous aurez plusieurs T , et un Future (par opposition à une List ) ne peut en stocker qu'un. Le même problème se produit avec Option , en passant:

 scala> for (a <- Some(3); b <- list) yield (a, b) :9: error: type mismatch; found : List[(Int, Int)] required: Option[?] for (a <- Some(3); b <- list) yield (a, b) ^ 

La solution la plus simple consiste à imbriquer plusieurs multiples pour comprendre:

 scala> for { | list <- f | } yield for { | e <- list | } yield (e -> 1) res3: scala.concurrent.Future[List[(Ssortingng, Int)]] = scala.concurrent.im pl.Promise$DefaultPromise@4f498585 

Rétrospectivement, cette limitation aurait dû être assez évidente. Le problème est que pratiquement tous les exemples utilisent des collections, et toutes les collections ne sont que GenTraversableOnce , elles peuvent donc être mélangées librement. Ajoutons à cela que le mécanisme CanBuildFrom pour lequel Scala a été beaucoup critiqué permet de combiner des collections arbitraires et d'obtenir des types spécifiques au lieu de GenTraversableOnce .

Et, pour rendre les choses encore plus floues, Option peut être convertie en une Iterable , ce qui permet de combiner des options avec des collections tant que l'option ne vient pas en premier.

Mais la principale source de confusion, à mon avis, est que personne ne mentionne jamais cette limitation dans l'enseignement de la compréhension.

Hmm, je pense que je l’ai eu. J’ai besoin d’envelopper dans un avenir comme pour la compréhension ajoute une carte plate.

Cela marche:

 for { list <- f e <- Future( list ) } yield (e -> 1) 

Lorsque j’ai ajouté ci-dessus, je n’ai pas encore vu de réponse. Cependant, pour approfondir cela, il est possible de faire un travail dans le cadre de la compréhension. Je ne suis pas sûr que cela vaille la peine d’avenir (éditer: en utilisant le succès, il ne devrait pas y avoir de surcharge).

 for { list1 <- f list2 <- Future.successful( list1.map( _ -> 1) ) list3 <- Future.successful( list2.filter( _._2 == 1 ) ) } yield list3 

Addendum, une demi-année plus tard.

Une autre façon de résoudre ce problème consiste à utiliser simplement assignation = au lieu de <- lorsque vous avez un autre type que le type de retour initial de la carte.

Lors de l'utilisation de l'affectation, cette ligne n'est pas mise à plat. Vous êtes maintenant libre de faire une carte explicite (ou une autre transformation) qui renvoie un type différent.

Ceci est utile si vous avez plusieurs transformations pour lesquelles une étape n’a pas le même type de retour que les autres étapes, mais vous souhaitez quand même utiliser la syntaxe for-comprehension car elle rend votre code plus lisible.

 import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future val f = Future( List("A", "B", "C") ) def longRunning( l:List[(Ssortingng, Int)] ) = Future.successful( l.map(_._2) ) for { list <- f e = list.map( _ -> 1 ) s <- longRunning( e ) } yield s 

Votre version originale ne comstack pas car List et Future sont des monades différentes. Pour voir pourquoi cela pose un problème, considérez ce qu’il veut pour:

 f.flatMap(list => list.map(e => e -> 1)) 

Clairement, list.map(_ -> 1) est une liste de paires (Ssortingng, Int) , donc l’argument de notre flatMap est une fonction qui mappe les listes de chaînes aux listes de ces paires. Mais nous avons besoin de quelque chose qui mappe les listes de chaînes vers un Future quelconque. Donc, cela ne comstack pas.

La version dans votre réponse comstack, mais elle ne fait pas ce que vous voulez. Il désuet à ceci:

 f.flatMap(list => Future(list).map(e => e -> 1)) 

Les types s’alignent cette fois-ci, mais nous ne faisons rien d’intéressant – nous tirons simplement la valeur du Future , nous le replaçons dans un Future et nous cartographions le résultat. Donc nous nous retrouvons avec quelque chose de type Future[(List[Ssortingng], Int)] , quand nous voulions un Future[List[(Ssortingng, Int)]] .

Ce que vous faites est une sorte de double opération de cartographie avec deux monades nestedes (différentes), et cela ne vous aidera pas. Heureusement, f.map(_.map(_ -> 1)) fait exactement ce que vous voulez et est clair et concis.

Je trouve cette forme plus lisible que la carte série ou le rendement en série:

 for (vs <- future(data); xs = for (x <- vs) yield g(x) ) yield xs 

au désortingment de la carte de tupling:

 f.map((_, xs)).map(_._2) 

ou plus précisément:

 f.map((vs: List[Int]) => (vs, for (x <- vs) yield g(x))).map(_._2)