链接 Scala 嵌套期货
Chaining Scala nested futures
我在 Scala 中链接期货时遇到了一些麻烦。假设我有以下内容:
def fetchInts(s: String): Future[List[Int]] = Future {
(0 to s.length).toList
}
def fetchChars(a: Int): Future[List[Char]] = Future {
("a" * a).toList
}
并且对于给定的 String
我想获取 Int
s 然后对于每个获取的 Int
我想获取 Char
s,尽可能异步.我想要得到的输出类型是 Future[List[(Int, List[Char])]]
并且在语义上我想做这样的事情:
def fetch(s: String): Future[List[(Int, List[Char])]] = {
for {
as <- fetchInts(s)
a <- as
cs <- fetchChars(a)
} yield (a, cs)
}
上面不会进行类型检查,因为我混合了 List
和 Future
monad,所以到目前为止我想出了:
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)
}
}
这种类型检查但看起来很笨拙,我怀疑这是否是线程优化的,因为我没有完全掌握 Futures.sequence()
的语义。是否有其他更简单、惯用的方法来执行此操作?
Scala 中的 for-comprehension,是同一实体上的地图和平面地图。在您的获取方法中,它超过了期货。它不起作用的原因是 as
是 List[Int]
,而不是未来。在这个用例中,不用 for-comprehension 写起来会更容易。您可以尝试以下方法:
def fetch(s: String): Future[List[(Int, List[Char])]] = {
fetchInts(s).flatMap { ints =>
Future.traverse(ints)(i =>
fetchChars(i).map { chars =>
i -> chars
}
)
}
}
结果:
println(Await.result(fetch("abrakadabra"), 2.seconds))
是:
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)))
来自 Scala 文档 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.
代码 运行 在 Scastie。
如果你毕竟想要理解,可以试试:
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
}
我在 Scala 中链接期货时遇到了一些麻烦。假设我有以下内容:
def fetchInts(s: String): Future[List[Int]] = Future {
(0 to s.length).toList
}
def fetchChars(a: Int): Future[List[Char]] = Future {
("a" * a).toList
}
并且对于给定的 String
我想获取 Int
s 然后对于每个获取的 Int
我想获取 Char
s,尽可能异步.我想要得到的输出类型是 Future[List[(Int, List[Char])]]
并且在语义上我想做这样的事情:
def fetch(s: String): Future[List[(Int, List[Char])]] = {
for {
as <- fetchInts(s)
a <- as
cs <- fetchChars(a)
} yield (a, cs)
}
上面不会进行类型检查,因为我混合了 List
和 Future
monad,所以到目前为止我想出了:
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)
}
}
这种类型检查但看起来很笨拙,我怀疑这是否是线程优化的,因为我没有完全掌握 Futures.sequence()
的语义。是否有其他更简单、惯用的方法来执行此操作?
Scala 中的 for-comprehension,是同一实体上的地图和平面地图。在您的获取方法中,它超过了期货。它不起作用的原因是 as
是 List[Int]
,而不是未来。在这个用例中,不用 for-comprehension 写起来会更容易。您可以尝试以下方法:
def fetch(s: String): Future[List[(Int, List[Char])]] = {
fetchInts(s).flatMap { ints =>
Future.traverse(ints)(i =>
fetchChars(i).map { chars =>
i -> chars
}
)
}
}
结果:
println(Await.result(fetch("abrakadabra"), 2.seconds))
是:
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)))
来自 Scala 文档 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.
代码 运行 在 Scastie。
如果你毕竟想要理解,可以试试:
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
}