sensibles partes para mapa las imagen etiqueta emplea elementos elemento ejemplos ejemplo definir coords atributo activas scala akka future

scala - partes - etiqueta map html



¿Cómo convertir el mapa[A, futuro[B]] al futuro[mapa[A, B]]? (7)

¿Es esta solución aceptable? Sin un contexto de ejecución, esto debería funcionar ...

def removeMapFuture[A, B](in: Future[Map[A, Future[B]]]) = { in.flatMap { k => Future.sequence(k.map(l => l._2.map(l._1 -> _) )).map { p => p.toMap } } }

He estado trabajando con la biblioteca Scala Akka y he encontrado un pequeño problema. Como dice el título, necesito convertir el Map[A, Future[B]] al Future[Map[A,B]] . Sé que uno puede usar Future.sequence para Future.sequence como listas, pero eso no funciona en este caso.

Me preguntaba: ¿hay una forma limpia en Scala para hacer esta conversión?


Actualización: en realidad, puede obtener la buena sintaxis de secuencia en Scalaz 7 sin demasiada molestia :

import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.{ Future, future } import scalaz._, Scalaz.{ ToTraverseOps => _, _ } import scalaz.contrib.std._ val m = Map("a" -> future(1), "b" -> future(2), "c" -> future(3))

Y entonces:

scala> m.sequence.onSuccess { case result => println(result) } Map(a -> 1, b -> 2, c -> 3)

En principio, no debería ser necesario ocultar ToTraverseOps esta manera, pero por el momento hace el truco. Consulte el resto de mi respuesta a continuación para obtener más detalles sobre la clase de tipo Traverse , las dependencias, etc.

Como notas de copumpkin en un comentario anterior, Scalaz contiene una clase de tipo Traverse con una instancia para el Map[A, _] que es una de las piezas del rompecabezas aquí. La otra pieza es la instancia de Applicative para Future , que no se encuentra en Scalaz 7 (que aún se construye en forma cruzada contra la scalaz-contrib anterior de Future 2.9), pero está en scalaz-contrib .

import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scalaz._, Scalaz._ import scalaz.contrib.std._ def sequence[A, B](m: Map[A, Future[B]]): Future[Map[A, B]] = { type M[X] = Map[A, X] (m: M[Future[B]]).sequence }

O:

def sequence[A, B](m: Map[A, Future[B]]): Future[Map[A, B]] = Traverse[({ type L[X] = Map[A, X] })#L] sequence m

O:

def sequence[A, B](m: Map[A, Future[B]]): Future[Map[A, B]] = TraverseOpsUnapply(m).sequence

En un mundo perfecto, sería capaz de escribir m.sequence , pero la maquinaria de TraverseOps que debería hacer posible esta sintaxis actualmente no puede decir cómo pasar de una instancia de Map particular a la instancia de Traverse adecuada.


Creo que lo más sucinto que podemos ser con Scala es

val map = Map("a" -> future{1}, "b" -> future{2}, "c" -> future{3}) Future.traverse(map) { case (k, fv) => fv.map(k -> _) } map(_.toMap)


Esto también funciona, donde la idea es usar el resultado de la secuencia (de los valores del mapa) para lanzar una promesa que dice que puedes comenzar a recuperar valores de tu mapa. mapValues le ofrece una vista no estricta de su mapa , por lo que value.get.get solo se aplica cuando recupera el valor. Así es, puedes mantener tu mapa! Anuncio gratis para los puzzles en ese enlace .

import concurrent._ import concurrent.duration._ import scala.util._ import ExecutionContext.Implicits.global object Test extends App { def calc(i: Int) = { Thread sleep i * 1000L ; i } val m = Map("a" -> future{calc(1)}, "b" -> future{calc(2)}, "c" -> future{calc(3)}) val m2 = m mapValues (_.value.get.get) val k = Future sequence m.values val p = Promise[Map[String,Int]] k onFailure { case t: Throwable => p failure t } k onSuccess { case _ => p success m2 } val res = Await.result(p.future, Duration.Inf) Console println res }

Aquí está el REPL donde lo ve forzar el mapa de m2 imprimiendo todos sus valores:

scala> val m2 = m mapValues (_.value.get.get) m2: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2, c -> 3)

Esto muestra lo mismo con futuros que aún están en el futuro:

scala> val m2 = m mapValues (_.value.get.get) java.util.NoSuchElementException: None.get


Intentaría evitar el uso de soluciones superfuncionales basadas en Scalaz (a menos que su proyecto ya tenga una gran base de Scalaz y tenga toneladas de código "computacionalmente sofisticado"; no se ofenda por el comentario de "overengineered"):

// the map you have val foo: Map[A, Future[B]] = ??? // get a Seq[Future[...]] so that we can run Future.sequence on it val bar: Seq[Future[(A, B)]] = foo.map { case (k, v) => v.map(k -> _) } // here you go; convert back `toMap` once it completes Future.sequence(bar).onComplete { data => // do something with data.toMap }

Sin embargo , debe ser seguro asumir que los valores de su mapa se generan de alguna manera a partir de las claves del mapa, que inicialmente residen en una Seq como la List , y que la parte del código que crea el Map inicial está bajo su control en lugar de enviarse. de otro lado. Por lo tanto, personalmente tomaría un enfoque aún más simple / limpio al no comenzar con el Map[A, Future[B]] en primer lugar.

def fetchAgeFromDb(name: String): Future[Int] = ??? // no foo needed anymore // no Map at all before the future completes val bar = personNames.map { name => fetchAgeFromDb(name).map(name -> _) } // just as above Future.sequence(bar).onComplete { data => // do something with data.toMap }


Simplemente cree un nuevo futuro que espere todos los futuros en los valores del mapa, luego construya un mapa para devolver.


Vea si esto funciona para usted:

val map = Map("a" -> future{1}, "b" -> future{2}, "c" -> future{3}) val fut = Future.sequence(map.map(entry => entry._2.map(i => (entry._1, i)))).map(_.toMap)

La idea es asignar el mapa a un Iterable para una Tuple de la clave del mapa y el resultado del futuro vinculado a esa clave. Desde allí, puede sequence ese Iterable y luego, una vez que tenga el Future agregado, toMap y convertir ese Iterable de Tuples en un mapa a través de toMap .

Ahora, una alternativa a este enfoque es intentar hacer algo similar a lo que hace la función de sequence , con un par de ajustes. Podrías escribir una función sequenceMap así:

def sequenceMap[A, B](in: Map[B, Future[A]])(implicit executor: ExecutionContext): Future[Map[B, A]] = { val mb = new MapBuilder[B,A, Map[B,A]](Map()) in.foldLeft(Promise.successful(mb).future) { (fr, fa) => for (r <- fr; a <- fa._2.asInstanceOf[Future[A]]) yield (r += ((fa._1, a))) } map (_.result) }

Y luego usarlo en un ejemplo como este:

val map = Map("a" -> future{1}, "b" -> future{2}, "c" -> future{3}) val fut = sequenceMap(map) fut onComplete{ case Success(m) => println(m) case Failure(ex) => ex.printStackTrace() }

Esto podría ser un poco más eficiente que el primer ejemplo, ya que crea menos colecciones intermedias y tiene menos visitas al ExecutionContext .