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
.