2013-03-01 1 views
8

J'ai une liste d'ID de chaîne représentant des enregistrements DB. Je voudrais les charger à partir de la base de données de manière asynchrone, puis télécharger chaque enregistrement sur un serveur distant de manière asynchrone, puis, une fois le téléchargement terminé, enregistrer les identifiants des enregistrements téléchargés. Depuis que je suis sur Scala 2.9.2, j'utilise l'implémentation Future de core-util de Twitter, mais elle devrait fonctionner exactement comme les futures 2.10 en termes de transformations Monadic.TraversableOnce, Future et Option dans une Scala pour la compréhension

Le concept général est le suivant:

def fetch(id: String): Future[Option[Record]] 
def upload(record: Record): Future[String] 
def notifyUploaded(ids: Seq[String]): Unit 

val ids: Seq[String] = .... 

Je suis en train de le faire via une pour la compréhension, mais le fait que le rendement d'un avenir chercher de l'option rend obscure et le code ne compile pas:

for { 
    id <- ids 
    maybeRecord <- fetch(id) 
    record <- maybeRecord 
    uploadedId <- upload(record) 
} yield uploadedId 

Compiler cela se traduit par l'erreur suivante:

scala: type mismatch; 
found : com.twitter.util.Future[String] 
required: Option[?] 
    uploadedId <- upload(record) 
       ^

Qu'est-ce que je mi ssing? pourquoi le compilateur attend-il que uploadedId soit une option? Y at-il une jolie façon de travailler autour de ça?

+0

double possible de [Type Mismatch sur Scala Pour Comprehension] (http://stackoverflow.com/questions/4719592/type-mismatch-on-scala-for-comprehension) –

+1

A Monad est un Monoid dans la catégorie de ** Endo ** - foncteurs. Il suffit de dire ' – folone

+1

@folone: ​​Je crains que tout le monde ne recevra pas la [blague] (http://stackoverflow.com/questions/3870088/a-monad-is-just-a-monoid-in-the-category-of -endofunctors-quoi-le-problème). Justin ' –

Répondre

6

la signature de Tenir compte de la fonction flatMap (ou lier):

trait Monad[M[_]] { 
    def flatMap[A](a : M[A], f : A => M[B]) : M[B] 
    .... 

Dans votre cas, vous essayez d'utiliser flatMap sur un Option, en lui donnant un f qui génère un Future. Mais comme dans la signature ci-dessus, f devrait générer quelque chose dans la même monade que celle à laquelle il a été appelé. Scala n'est pas forcément très utile à cet égard, car il est très bon pour convertir les choses (par exemple en Seq s) de telle sorte que vous ayez l'impression que vous pouvez enchaîner des appels arbitraires flatMap du conteneur.

Ce que vous voulez peut-être, c'est un «transformateur Monad», qui vous donne la possibilité de composer des monades. Debasish Ghosh a publié un article sur l'utilisation des transformateurs Mona Scalaz here.

0

Vous ne pouvez pas mélanger tous les types différents en un pour la compréhension, j'ai compris que vous pourriez mélanger Seq et Option et le résultat serait soit Seq ou Option en fonction de ce qui est en premier. Il n'est pas possible de mélanger Future et Seq ou Option. Si vous voulez utiliser pour la compréhension, vous devrez les cascader peu. Dans de tels cas, il pourrait être plus agréable avec map/flatMap. J'ai implémenté votre question dans les deux sens et ajouté des types à quelques résultats intermédiaires afin que vous puissiez voir le gâchis qui est créé en travaillant avec tous ces types différents.

object TestClass { 

    import scala.concurrent.Future 
    import scala.concurrent.ExecutionContext.Implicits.global 
    import scala.concurrent._ 
    import scala.concurrent.duration._ 

    case class Record(id: String) 


    def fetch(id: String): Future[Option[Record]] = Future { 
    Thread.sleep(1000); 
    Some(Record(id)) 
    } 

    def upload(record: Record): Future[String] = Future { 
    Thread.sleep(3000); 
    record.id + "_uploaded" 
    } 

    def notifyUploaded(ids: Seq[String]): Unit = println("notified" + ids) 

    val ids: Seq[String] = Seq("a", "b", "c") 

    def main(args: Array[String]): Unit = { 
    forComprehensionImpl() 
    mapAndFlatMapImpl() 
    } 

    def forComprehensionImpl() = { 
    val result: Seq[Future[Option[Future[String]]]] = for { 
     id <- ids 
    } yield { 
     for { 
     maybeRecord <- fetch(id) 
     } yield { 
     for { 
      record <- maybeRecord 
     } yield { 
      for { 
      uploadedId <- upload(record) 
      } yield uploadedId 
     } 
     } 
    } 
    val result2: Future[Seq[Option[Future[String]]]] = Future.sequence(result) 
    val result3: Future[Unit] = result2.flatMap { x: Seq[Option[Future[String]]] => 
     Future.sequence(x.flatten).map(notifyUploaded) 
    } 
    Await.result(result3, Duration.Inf) 
    } 


    def mapAndFlatMapImpl() = { 
    val res: Seq[Future[Iterable[String]]] = ids.map { id => 
     fetch(id).flatMap { maybeRecord => 
     val res1: Option[Future[Seq[String]]] = maybeRecord.map { record => 
      upload(record) map (Seq(_)) 
     } 
     res1 match { 
      case Some(a) => a 
      case None => Future(Seq()) 
     } 
     } 
    } 
    val res3: Future[Unit] = Future.sequence(res) map (a => notifyUploaded(a.flatten)) 
    Await.result(res3, Duration.Inf) 
    } 
} 
Questions connexes