无法用于理解以在Future中映射List

时间:2013-01-16 00:39:49

标签: scala future

我有这个问题,我每次都要解决这个问题。我不能使用for comprehension来映射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 comprehension来做到这一点,那么它在我的另一个例子中是否适用于我不进行flatmap的原因?我正在使用Scala 2.10.0。

4 个答案:

答案 0 :(得分:64)

好吧,当你有一个单独的多个生成器用于理解时,你展平结果类型。也就是说,不是获得List[List[T]],而是获得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))

现在,你如何压扁Future[List[T]]?它不能是Future[T],因为您将获得多个T,而Future(而不是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)
                                   ^

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

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

回想起来,这种限制应该是非常明显的。问题在于,几乎所有示例都使用集合,并且所有集合都只是GenTraversableOnce,因此它们可以自由混合。除此之外,Scala受到批评的CanBuildFrom机制可以混合使用任意集合并返回特定类型,而不是GenTraversableOnce

而且,为了使事情变得更加模糊,可以将Option转换为Iterable,这样就可以将选项与集合组合在一起,只要该选项不是第一个。

但在我看来,混淆的主要原因是,在教导理解时,没有人提到过这种限制。

答案 1 :(得分:8)

嗯,我想我明白了。我需要在未来包装,因为for comprehension添加了flatmap。

这有效:

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

答案 2 :(得分:5)

您的原始版本无法编译,因为ListFuture是不同的monad。要了解为什么这是一个问题,请考虑它的后果:

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中提取价值,将其重新放回Future,然后映射到结果。因此,当我们需要Future[(List[String], Int)]

时,我们会得到Future[List[(String, Int)]]类型的内容

你正在做的是一种具有两个(不同的)嵌套monad的双映射操作,而这不是for - 理解能够帮助你的东西。幸运的是,f.map(_.map(_ -> 1))完全符合您的要求,并且简洁明了。

答案 3 :(得分: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)