Scala - 意外类型从Map切换到Iterable进行理解?

Kva*_*ass 5 scala for-comprehension

为了理解地图,我对幕后打字感到困惑.我的理解是外部集合类型通常应该被保留,我们在以下两种情况下看到预期的行为:

scala> for {
     |   (k,v) <- Map(0->1,2->3)
     | } yield k -> v
res0: scala.collection.immutable.Map[Int,Int] = Map(0 -> 1, 2 -> 3)

scala> for {
     |   (k,v) <- Map(0->1,2->3)
     |   foo = 1
     | } yield k -> v
res1: scala.collection.immutable.Map[Int,Int] = Map(0 -> 1, 2 -> 3)
Run Code Online (Sandbox Code Playgroud)

但是当我在for comprehension中添加第二个作业时,我得到了一些令人惊讶的东西:

scala> for {
     |   (k,v) <- Map(0->1,2->3)
     |   foo = 1
     |   bar = 2
     | } yield k -> v
res2: scala.collection.immutable.Iterable[(Int, Int)] = List((0,1), (2,3))
Run Code Online (Sandbox Code Playgroud)

为什么会这样?

dhg*_*dhg 5

如果你运行scala -Xprint:typer -e "for { ... } yield k->v",你可以得到一个去糖的版本的代码.这是你得到的非常简化的版本:

val m: Map[Int,Int] = Map(0->1, 2->3)
m.map {
  case x @ (k,v) =>
    val foo = 1
    val bar = 2
    (x, foo, bar)
}.map {
  case ((k,v), foo, bar) => (k, v)
}
Run Code Online (Sandbox Code Playgroud)

所以你会注意到,当for-comprehension转换为一个.map调用时,它实际上是返回foobar伴随着k->v,这意味着它是一个Tuple3[(Int,Int), Int, Int].由于可迭代的Tuple3对象不能转换为a Map,因此它假定它必须返回一个Iterable.但是,为了获得正确的输出,这是一个Tuple2对象的集合,它执行一个.map丢弃foobar从中删除的二级Tuple3,但此时它不再知道它应该是一个Map因为当你链接调用.map该信息时不会发扬光大.

只有一个作业的例子很幸运,因为中间表示是Tuple2[(Int,Int), Int].

另一方面,如果您.map直接使用,它的工作原理:

Map(0->1, 2->3).map { 
  case (k,v) =>
    val foo = 1
    val bar = 2 
    k -> v
}
Run Code Online (Sandbox Code Playgroud)