Scala 为什么 flatMap 对待 (x => x) 不同于 (identity)
Scala why flatMap treats (x => x) different than (identity)
首先,map
对待 x => x
与 identity
相同
List(1,2,3).map(x => x) //res0: List[Int] = List(1, 2, 3)
List(1,2,3).map(identity) //res1: List[Int] = List(1, 2, 3)
现在让我们将 List[Option[Int]] 转换为 List[Int] 并丢弃所有 None。我们可以通过 .flatten
来做到这一点。但是这个问题的重点是了解 flatMap
如何对待 identity
val maybeNums = List(Some(1), None, Some(-2), None, None, Some(33))
// Works OK, result = List[Int] = List(1, -2, 33)
maybeNums.flatMap(x => x)
maybeNums.flatMap(x => x.map(identity))
// Not working:
maybeNums.flatMap(identity)
Error:(5, 20) type mismatch;
found : Option[Int] => Option[Int]
required: Option[Int] => scala.collection.GenTraversableOnce[?]
问题:为什么 maybeNums.flatMap(identity)
编译出错,而 maybeNums.flatMap(x => x)
正常?
有趣的是我遇到了和你类似的问题。此行为是由于 Option
不是 GenTraversableOnce
但存在到 1 的隐式转换引起的。编译器会告诉你出了什么问题,但不幸的是,由于在 Scala 中经常出现这种情况,它并没有指出错误的真正原因。如果您的集合包含 GenTraversableOnce
类型的元素,flatMap 方法就可以正常工作。
起初我以为隐式转换可以解决这个问题,但事实证明eta扩展需要类型显式匹配。更有趣的是下面的代码可以编译:
ys.flatMap(identity(_))
我假设在这种情况下会发生从 Option[Int] => Option[Int]
到 Option[Int] => GenTraversableOnce[Int]
的隐式转换。
在x => x
的情况下对x
进行了前面提到的隐式转换所以代码可以编译
对 Lampart 的回答进行更详细的解释。
归结为类型推断在 Scala 中的工作方式以及预期类型的使用。
要使 maybeNums.flatMap(???)
起作用,???
必须具有类型 Option[Int] => GenTraversableOnce[?A]
(其中 ?A
代表某种未知类型)。这是预期的类型。
当 ???
是类似 x => x
的 lambda 表达式时,参数类型为 Option[Int]
,正文类型为预期类型 GenTraversableOnce[?A]
。主体 没有 预期类型的类型是 Option[Int]
,因此找到并插入从 Option[Int]
到 GenTraversableOnce[Int]
的隐式转换。
当 ???
为 identity
时,它是 identity[?B]
的缩写,表示某些未知类型 ?B
(与上面的 ?A
意义相同,但当然,它们不必相同),类型 ?B => ?B
也是如此。所以编译器需要求解一个有两个未知类型的方程:?B => ?B == Option[Int] => GenTraversableOnce[?A]
。它匹配参数类型,为 ?B
选择 ?B = Option[Int]
,但找不到合适的 ?A
。打印错误时, ?B
被替换,我在上面写为 ?A
的类型打印为 ?
(因为它是唯一剩下的未知数)。
如果 identity(_)
,则扩展为 x => identity(x)
。同样,参数的类型被推断为 Option[Int]
并且主体已计算类型 Option[Int]
和预期类型 GenTraversableOnce[?A]
.
首先,map
对待 x => x
与 identity
List(1,2,3).map(x => x) //res0: List[Int] = List(1, 2, 3)
List(1,2,3).map(identity) //res1: List[Int] = List(1, 2, 3)
现在让我们将 List[Option[Int]] 转换为 List[Int] 并丢弃所有 None。我们可以通过 .flatten
来做到这一点。但是这个问题的重点是了解 flatMap
如何对待 identity
val maybeNums = List(Some(1), None, Some(-2), None, None, Some(33))
// Works OK, result = List[Int] = List(1, -2, 33)
maybeNums.flatMap(x => x)
maybeNums.flatMap(x => x.map(identity))
// Not working:
maybeNums.flatMap(identity)
Error:(5, 20) type mismatch;
found : Option[Int] => Option[Int]
required: Option[Int] => scala.collection.GenTraversableOnce[?]
问题:为什么 maybeNums.flatMap(identity)
编译出错,而 maybeNums.flatMap(x => x)
正常?
有趣的是我遇到了和你类似的问题。此行为是由于 Option
不是 GenTraversableOnce
但存在到 1 的隐式转换引起的。编译器会告诉你出了什么问题,但不幸的是,由于在 Scala 中经常出现这种情况,它并没有指出错误的真正原因。如果您的集合包含 GenTraversableOnce
类型的元素,flatMap 方法就可以正常工作。
起初我以为隐式转换可以解决这个问题,但事实证明eta扩展需要类型显式匹配。更有趣的是下面的代码可以编译:
ys.flatMap(identity(_))
我假设在这种情况下会发生从 Option[Int] => Option[Int]
到 Option[Int] => GenTraversableOnce[Int]
的隐式转换。
在x => x
的情况下对x
进行了前面提到的隐式转换所以代码可以编译
对 Lampart 的回答进行更详细的解释。
归结为类型推断在 Scala 中的工作方式以及预期类型的使用。
要使 maybeNums.flatMap(???)
起作用,???
必须具有类型 Option[Int] => GenTraversableOnce[?A]
(其中 ?A
代表某种未知类型)。这是预期的类型。
当 ???
是类似 x => x
的 lambda 表达式时,参数类型为 Option[Int]
,正文类型为预期类型 GenTraversableOnce[?A]
。主体 没有 预期类型的类型是 Option[Int]
,因此找到并插入从 Option[Int]
到 GenTraversableOnce[Int]
的隐式转换。
当 ???
为 identity
时,它是 identity[?B]
的缩写,表示某些未知类型 ?B
(与上面的 ?A
意义相同,但当然,它们不必相同),类型 ?B => ?B
也是如此。所以编译器需要求解一个有两个未知类型的方程:?B => ?B == Option[Int] => GenTraversableOnce[?A]
。它匹配参数类型,为 ?B
选择 ?B = Option[Int]
,但找不到合适的 ?A
。打印错误时, ?B
被替换,我在上面写为 ?A
的类型打印为 ?
(因为它是唯一剩下的未知数)。
如果 identity(_)
,则扩展为 x => identity(x)
。同样,参数的类型被推断为 Option[Int]
并且主体已计算类型 Option[Int]
和预期类型 GenTraversableOnce[?A]
.