Map 中的 flatMap 如何在 Scala 中工作?

How flatMap in a Map works in scala?

这是我的代码

def testMap() = {
    val x = Map(
      1 -> Map(
        2 -> 3,
        3 -> 4
      ),
      5 -> Map(
        6 -> 7,
        7 -> 8
      )
    )

    for {
      (a, v) <- x
      (b, c) <- v
    } yield {
      a
    }
  }

上面的代码给出了

List(1, 1, 5, 5)

如果我将 for comprehension a 的 yield 值更改为 (a, b),结果是

Map(1 -> 3, 5 -> 7)

如果我把(a, b)改成(a, b, c),结果就是

List((1,2,3), (1,3,4), (5,6,7), (5,7,8))

我的问题是为了理解,确定结果类型背后的机制是什么?

当您查看 API 文档以了解 map-Method 的详细信息时,您会发现它有第二个类型为 CanBuildFrom 的隐式参数。 CanBuildFrom 的实例定义了在映射到其他集合并提供特定元素类型时如何构建特定集合。

在您得到 Map 作为结果的情况下,您是在 Map 上进行映射并提供二进制元组。所以编译器搜索一个 CanBuildFrom 实例,它可以处理那个。

为了找到这样的实例,编译器会在不同的地方查找,例如当前作用域,class 调用方法及其伴生对象。 在这种情况下,它将在 Map 的伴生对象中找到一个名为 canBuildFrom 的隐式字段,该字段是合适的,可用于构建 Map 作为结果。因此它尝试将结果类型推断为 Map 并在成功时使用此实例。

在这种情况下,您提供单个值或三元组,在 Map 的同伴中找到的实例没有所需的类型,因此它将继续向上搜索继承树。它在 Iterable 的伴随对象中找到它。它们允许构建任意元素类型的 Iterable 的实例。所以编译器使用它。

那你为什么会得到一个 List?因为这恰好是那里使用的实现,类型系统只保证你 Iterable.

如果你想得到一个 Iterable 而不是 Map 你可以显式地提供一个 CanBuildFrom 实例(仅当你直接调用 map 和 flatMap 时)或者只是强制 return类型。在那里你还会注意到你将无法请求 List 即使你得到了一个。

这行不通:

val l: List[Int] = Map(1->2).map(x=>3)

但是这将:

val l: Iterable[Int] = Map(1->2).map(x=>3)

要添加到@dth,如果你想要一个列表,你可以这样做:

val l = Map(1->2,3->4).view.map( ... ).toList

这里的 map 函数应用在一个惰性的 IterableView 上,它也输出一个 IterableView,实际构建是由 toList.

触发的

注意:另外,不使用 view 会导致危险行为。示例:

val m = Map(2->2,3->3)

val l = m.map{ case (k,v) => (k/2,v) ).toList
// List((1,3))

val l = m.view.map{ case (k,v) => (k/2,v) ).toList
// List((1,2), (1,3))

在这里,省略 .view 使地图输出一个覆盖重复键的地图(并做额外和不必要的工作)。