29

我一直在使用 Scala Akka 库,遇到了一些问题。正如标题所说,我需要转换Map[A, Future[B]]Future[Map[A,B]]. 我知道可以将Future.sequenceLists 用于 Iterables,但在这种情况下不起作用。

我想知道:Scala 中有没有一种干净的方法来进行这种转换?

4

7 回答 7

30

看看这是否适合你:

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)

这个想法是将映射映射到映射的键的IterableaTuple以及与该键相关的未来的结果。从那里你可以sequenceIterable然后一旦你有聚合Future,映射它并通过它转换IterableTuples映射toMap

现在,这种方法的另一种方法是尝试做一些类似于sequence函数正在做的事情,并进行一些调整。你可以写一个sequenceMap这样的函数:

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)
}

然后在这样的示例中使用它:

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()
}

这可能比第一个示例更有效,因为它创建的中间集合更少并且对ExecutionContext.

于 2013-07-04T23:58:33.033 回答
13

我认为核心 Scala 2.12.x 最简洁的是

val futureMap = Map("a" -> future{1}, "b" -> future{2}, "c" -> future{3}) 

Future.traverse(futureMap.toList) { case (k, fv) => fv.map(k -> _) } map(_.toMap)
于 2014-08-27T01:13:26.070 回答
9

更新:您实际上可以在Scalaz 7.sequence中获得不错的语法,而无需大惊小怪:

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))

接着:

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

原则上不应该ToTraverseOps像这样隐藏,但现在它可以解决问题。Traverse有关类型类、依赖项等的更多详细信息,请参见下面我的答案的其余部分。


正如copumpkin在上面的评论中指出的那样,Scalaz 包含一个带有实例的Traverse类型类Map[A, _],这是这里的拼图之一。另一Applicative部分是. Future_Futurescalaz-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
}

或者:

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

或者:

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

在一个完美的世界中,您可以编写m.sequence,但是TraverseOps应该使这种语法成为可能的机器目前无法告诉如何从特定Map实例转到适当的Traverse实例。

于 2013-07-05T13:46:02.770 回答
4

这也有效,其想法是使用(地图值的)序列结果来触发一个承诺,表明您可以开始从地图中检索值。 mapValues为您提供地图的非严格视图,因此value.get.get仅在您检索值时应用。没错,你可以保留你的地图!该链接中的谜题者的免费广告。

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
}

这是 REPL,您可以在其中看到它通过打印所有值来强制 m2 地图:

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

这与仍在未来的期货显示了同样的事情:

scala>   val m2 = m mapValues (_.value.get.get)
java.util.NoSuchElementException: None.get
于 2013-07-05T00:52:10.247 回答
1

我会尽量避免使用过度设计的基于 Scalaz 的超级功能解决方案(除非您的项目已经大量基于 Scalaz 并且有大量“计算复杂”的代码;对“过度设计”的评论没有冒犯):

// 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
}

但是,可以安全地假设您的映射值是以某种方式从映射键生成的,这些键最初驻留在Seq例如 中List,并且构建初始Map值的代码部分在您的控制之下,而不是从其他地方发送。所以我个人会采取一种更简单/更清洁的方法,而不是一开始就没有Map[A, Future[B]]

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
}
于 2014-04-01T20:47:26.293 回答
1

只需创建一个新的未来,它等待地图值中的所有未来,然后构建一个地图以返回。

于 2013-07-04T23:53:57.460 回答
0

这个解决方案是否可以接受:没有执行上下文,这应该可以工作......

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
    }
  }
}
于 2017-01-04T11:20:27.377 回答