I have some troubles chaining futures in Scala. Suppose I have the following:
def fetchInts(s: String): Future[List[Int]] = Future {
(0 to s.length).toList
}
def fetchChars(a: Int): Future[List[Char]] = Future {
("a" * a).toList
}
and for a given String
I want to fetch Int
s and then for each fetched Int
I want to fetch Char
s, all as async as possible. The output type I want to get is Future[List[(Int, List[Char])]]
and semantically I want to do something like that:
def fetch(s: String): Future[List[(Int, List[Char])]] = {
for {
as <- fetchInts(s)
a <- as
cs <- fetchChars(a)
} yield (a, cs)
}
The above will not type-check as I mix List
and Future
monads so I have so far come up with:
def fetch(s: String): Future[List[(Int, List[Char])]] = {
val fas: Future[List[Int]] = fetchInts(s)
fas.flatMap { as: List[Int] =>
val res = as.map { a: Int =>
val fcs: Future[List[Char]] = fetchChars(a)
fcs.flatMap(cs => Future((a, cs)))
}
Future.sequence(res)
}
}
This type-checks but looks clumsy and I have some doubts if this is thread-optimal as I do not fully grasp the semantics of Futures.sequence()
. Is there some other simpler, idiomatic way to perform this?
for-comprehension in Scala, is maps and flatMaps over the same entity. In your fetch method, it is over futures. The reason it doesn't work, is that as
is List[Int]
, and not a future. In this use case, it will be easier to write it without for-comprehension. You can try the following:
def fetch(s: String): Future[List[(Int, List[Char])]] = {
fetchInts(s).flatMap { ints =>
Future.traverse(ints)(i =>
fetchChars(i).map { chars =>
i -> chars
}
)
}
}
The result of:
println(Await.result(fetch("abrakadabra"), 2.seconds))
Is:
List((0,List()), (1,List(a)), (2,List(a, a)), (3,List(a, a, a)), (4,List(a, a, a, a)), (5,List(a, a, a, a, a)), (6,List(a, a, a, a, a, a)), (7,List(a, a, a, a, a, a, a)), (8,List(a, a, a, a, a, a, a, a)), (9,List(a, a, a, a, a, a, a, a, a)), (10,List(a, a, a, a, a, a, a, a, a, a)), (11,List(a, a, a, a, a, a, a, a, a, a, a)))
From Scala docs Future.traverse :
Asynchronously and non-blockingly transforms a IterableOnce[A] into a Future[IterableOnce[B]] using the provided function A => Future[B]. This is useful for performing a parallel map.
Code run at Scastie .
If after all you do want to use for comprehension, you can try:
def fetch1(s: String): Future[List[(Int, List[Char])]] = {
for {
ints <- fetchInts(s)
result <- Future.traverse(ints)(i => for {
chars <- fetchChars(i)
} yield i -> chars)
} yield result
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.