我有这个问题,我每次都要解决这个问题。我不能使用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。
答案 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 {
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)
您的原始版本无法编译,因为List
和Future
是不同的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)