Scala:带有元组的 flatMap
Scala: flatMap with tuples
为什么下面的语句对 .map()
有效但对 .flatMap()
无效?
val tupled = input.map(x => (x*2, x*3))
//Compilation error: cannot resolve reference flatMap with such signature
val tupled = input.flatMap(x => (x*2, x*3))
这个语句没有问题,但是:
val tupled = input.flatMap(x => List(x*2, x*3))
假设 input
如果类型为 List[Int]
,map
接受从 Int
到 A
的函数,而 flatMap
接受一个函数从 Int
到 List[A]
.
根据您的使用情况,您可以选择其中之一,但它们绝对不可互换。
例如,如果您只是转换 List
的元素,您通常希望使用 map
:
List(1, 2, 3).map(x => x * 2) // List(2, 4, 6)
但是你想改变 List
的结构 - 例如 - "explode" 每个元素到另一个列表然后展平它们,flatMap
是你的朋友:
List(1, 2, 3).flatMap(x => List.fill(x)(x)) // List(1, 2, 2, 3, 3, 3)
使用 map
你会得到 List(List(1), List(2, 2), List(3, 3, 3))
。
要了解这是如何工作的,显式解压缩您发送给 map
和 flatMap
的函数并检查它们的签名会很有用。我在这里重写了它们,所以你可以看到 f
是一个从 Int
映射到 (Int, Int)
元组的函数,而 g
是一个从 Int
到 List[Int]
.
val f: (Int) => (Int, Int) = x => (x*2, x*3)
val g: (Int) => List[Int] = x => List(x*2, x*3)
List(1,2,3).map(f)
//res0: List[(Int, Int)] = List((2,3), (4,6), (6,9))
List(1,2,3).map(g)
//res1: List[List[Int]] = List(List(2, 3), List(4, 6), List(6, 9))
//List(1,2,3).flatMap(f) // This won't compile
List(1,2,3).flatMap(g)
//res2: List[Int] = List(2, 3, 4, 6, 6, 9)
那么为什么 flatMap(f)
不能编译呢?让我们看一下 flatMap
的签名,在本例中是从 List
实现中提取的:
final override def flatMap[B, That](f : scala.Function1[A, scala.collection.GenTraversableOnce[B]])(...)
解包有点困难,我省略了一些,但关键是GenTraversableOnce
类型。 List
,如果你沿着它的继承链往上看,将 this 作为它构建的特征,因此是一个从某种类型 A
映射到 List
(或任何对象)的函数具有 GenTraversableOnce
特性)将是一个有效的函数。值得注意的是,元组没有这个特性。
这是输入错误的粗略解释,值得解释,因为任何显示 'cannot resolve reference with such a signature' 的错误都意味着它无法找到采用您显式类型的函数'重新提供。在 Scala 中经常推断出类型,因此您可以很好地确保您提供的类型是您调用的方法所期望的类型。
请注意,flatMap
在函数式编程中具有标准含义,粗略地说,任何消耗单个元素并产生 n 个元素的映射函数,但您的最终结果是所有这些元素的串联列出。因此,您传递给 flatMap
的函数将始终期望生成一个列表,并且 flatMap
函数不会期望知道如何对单个元素进行操作。
为什么下面的语句对 .map()
有效但对 .flatMap()
无效?
val tupled = input.map(x => (x*2, x*3))
//Compilation error: cannot resolve reference flatMap with such signature
val tupled = input.flatMap(x => (x*2, x*3))
这个语句没有问题,但是:
val tupled = input.flatMap(x => List(x*2, x*3))
假设 input
如果类型为 List[Int]
,map
接受从 Int
到 A
的函数,而 flatMap
接受一个函数从 Int
到 List[A]
.
根据您的使用情况,您可以选择其中之一,但它们绝对不可互换。
例如,如果您只是转换 List
的元素,您通常希望使用 map
:
List(1, 2, 3).map(x => x * 2) // List(2, 4, 6)
但是你想改变 List
的结构 - 例如 - "explode" 每个元素到另一个列表然后展平它们,flatMap
是你的朋友:
List(1, 2, 3).flatMap(x => List.fill(x)(x)) // List(1, 2, 2, 3, 3, 3)
使用 map
你会得到 List(List(1), List(2, 2), List(3, 3, 3))
。
要了解这是如何工作的,显式解压缩您发送给 map
和 flatMap
的函数并检查它们的签名会很有用。我在这里重写了它们,所以你可以看到 f
是一个从 Int
映射到 (Int, Int)
元组的函数,而 g
是一个从 Int
到 List[Int]
.
val f: (Int) => (Int, Int) = x => (x*2, x*3)
val g: (Int) => List[Int] = x => List(x*2, x*3)
List(1,2,3).map(f)
//res0: List[(Int, Int)] = List((2,3), (4,6), (6,9))
List(1,2,3).map(g)
//res1: List[List[Int]] = List(List(2, 3), List(4, 6), List(6, 9))
//List(1,2,3).flatMap(f) // This won't compile
List(1,2,3).flatMap(g)
//res2: List[Int] = List(2, 3, 4, 6, 6, 9)
那么为什么 flatMap(f)
不能编译呢?让我们看一下 flatMap
的签名,在本例中是从 List
实现中提取的:
final override def flatMap[B, That](f : scala.Function1[A, scala.collection.GenTraversableOnce[B]])(...)
解包有点困难,我省略了一些,但关键是GenTraversableOnce
类型。 List
,如果你沿着它的继承链往上看,将 this 作为它构建的特征,因此是一个从某种类型 A
映射到 List
(或任何对象)的函数具有 GenTraversableOnce
特性)将是一个有效的函数。值得注意的是,元组没有这个特性。
这是输入错误的粗略解释,值得解释,因为任何显示 'cannot resolve reference with such a signature' 的错误都意味着它无法找到采用您显式类型的函数'重新提供。在 Scala 中经常推断出类型,因此您可以很好地确保您提供的类型是您调用的方法所期望的类型。
请注意,flatMap
在函数式编程中具有标准含义,粗略地说,任何消耗单个元素并产生 n 个元素的映射函数,但您的最终结果是所有这些元素的串联列出。因此,您传递给 flatMap
的函数将始终期望生成一个列表,并且 flatMap
函数不会期望知道如何对单个元素进行操作。