42

我有这个问题,我每次都必须解决。我无法使用 for 理解来映射 Future 中包含的内容。

例子:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

val f = Future( List("A", "B", "C") )
for {
  list <- f
  e <- list
} yield (e -> 1)

这给了我错误:

 error: type mismatch;
 found   : List[(String, Int)]
 required: scala.concurrent.Future[?]
              e <- list
                ^

但如果我这样做,它工作正常:

f.map( _.map( (_ -> 1) ) )

我是否应该无法通过使用 for 理解来做到这一点,它在我的另一个示例中起作用的原因是我不使用平面地图吗?我正在使用 Scala 2.10.0。

4

4 回答 4

66

好吧,当您在一个单一的理解中拥有多个生成器时,您正在扁平化生成的类型。也就是说List[List[T]],你得到的不是 a ,而是 a List[T]

scala> val list = List(1, 2, 3)
list: List[Int] = List(1, 2, 3)

scala> for (a <- list) yield for (b <- list) yield (a, b)
res0: List[List[(Int, Int)]] = List(List((1,1), (1,2), (1,3)), List((2,1
), (2,2), (2,3)), List((3,1), (3,2), (3,3)))

scala> for (a <- list; b <- list) yield (a, b)
res1: List[(Int, Int)] = List((1,1), (1,2), (1,3), (2,1), (2,2), (2,3),
(3,1), (3,2), (3,3))

现在,您将如何展平 a Future[List[T]]?它不能是 a Future[T],因为您将获得多个T,并且 a Future(与 a 相对List)只能存储其中一个。顺便说一句,同样的问题发生Option在:

scala> for (a <- Some(3); b <- list) yield (a, b)
<console>:9: error: type mismatch;
 found   : List[(Int, Int)]
 required: Option[?]
              for (a <- Some(3); b <- list) yield (a, b)
                                   ^

最简单的方法是简单地嵌套多个 for 理解:

scala> for {
     |   list <- f
     | } yield for {
     |   e <- list
     | } yield (e -> 1)
res3: scala.concurrent.Future[List[(String, Int)]] = scala.concurrent.im
pl.Promise$DefaultPromise@4f498585

回想起来,这个限制应该是非常明显的。问题是几乎所有的例子都使用了集合,而所有的集合都是 just GenTraversableOnce,所以它们可以自由混合。除此之外,Scala 备受批评的CanBuildFrom机制使得可以混合任意集合并获取特定类型,而不是GenTraversableOnce.

而且,为了使事情更加模糊,Option可以将其转换为Iterable,这使得可以将选项与集合结合起来,只要选项不是最先出现的。

但在我看来,混淆的主要来源是在教授理解力时没有人提到过这个限制。

于 2013-01-16T02:18:09.033 回答
8

嗯,我想我明白了。我需要在未来包装,因为 for 理解添加了一个平面图。

这有效:

for {
  list <- f
  e <- Future( list )
} yield (e -> 1)

当我在上面添加时,我还没有看到任何答案。但是,要对此进行扩展,可以在一个理解范围内进行工作。不确定是否值得未来的开销(编辑:通过使用成功应该没有开销)。

for {
  list1 <- f
  list2 <- Future.successful( list1.map( _ -> 1) )
  list3 <- Future.successful( list2.filter( _._2 == 1 ) )
} yield list3

半年后的补充。

解决此问题的另一种方法是简单地使用赋值=,而不是<-当您有其他类型而不是初始映射返回类型时。

使用分配时,该行不会得到平面映射。您现在可以自由地执行返回不同类型的显式映射(或其他转换)。

如果您有多个转换,其中一个步骤的返回类型与其他步骤不同,但您仍希望使用 for-comprehension 语法,这将很有用,因为它使您的代码更具可读性。

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

val f = Future( List("A", "B", "C") )
def longRunning( l:List[(String, Int)] ) = Future.successful( l.map(_._2) )

for {
  list <- f
  e = list.map( _ -> 1 )
  s <- longRunning( e )
} yield s
于 2013-01-16T00:44:03.650 回答
5

您的原始版本无法编译,因为List它们Future是不同的单子。要了解为什么这是一个问题,请考虑它对以下内容的影响:

f.flatMap(list => list.map(e => e -> 1))

显然list.map(_ -> 1)是一个(String, Int)对列表,所以我们的参数flatMap是一个将字符串列表映射到这些对列表的函数。但是我们需要一些将字符串列表映射到Future某种类型的东西。所以这不会编译。

您的答案中的版本确实可以编译,但它不能满足您的要求。它对此进行了反糖:

f.flatMap(list => Future(list).map(e => e -> 1))

这次类型排成一行,但我们没有做任何有趣的事情——我们只是将值从 中取出Future,将其放回 aFuture中,然后映射结果。所以Future[(List[String], Int)]当我们想要一个Future[List[(String, Int)]].

您正在做的是一种具有两个(不同)嵌套 monad 的双重映射操作,这不是for-comprehension 可以帮助您解决的问题。幸运的是f.map(_.map(_ -> 1)),它完全符合您的要求,并且简洁明了。

于 2013-01-16T01:26:45.040 回答
1

我发现这种形式比串行映射或串行产量更具可读性:

for (vs <- future(data);
     xs = for (x <- vs) yield g(x)
) yield xs

以元组映射为代价:

f.map((_, xs)).map(_._2)

或更准确地说:

f.map((vs: List[Int]) => (vs, for (x <- vs) yield g(x))).map(_._2)
于 2013-01-16T07:56:51.057 回答