私は Scala Akka ライブラリを使用していて、ちょっとした問題に遭遇しました。タイトルが示すように、私はに変換する必要がありMap[A, Future[B]]
ますFuture[Map[A,B]]
。Future.sequence
リストのようなイテラブルに使用できることは知っていますが、この場合は機能しません。
私は疑問に思っていました.Scalaでこの変換を行うためのきれいな方法はありますか?
これがうまくいくかどうかを確認してください:
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)
アイデアは、マップのキーのIterable
for にマップをマップしTuple
、そのキーに関連付けられた未来の結果をマップすることです。そこからそれができsequence
、Iterable
集約を取得したら、それをマップし、それを経由でマップにFuture
変換します。 Iterable
Tuples
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
.
コア 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)
これは、(マップの値の) シーケンス結果を使用して、マップから値の取得を開始できるという promise を起動するというアイデアでも機能します。 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
}
これは、すべての値を出力して m2 マップを強制する REPL です。
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
マップ値のすべての先物を待つ新しい未来を作成し、マップを構築して返すだけです。