flatMap 应用于 List[Option[T]] 时的行为
Behavior of flatMap when applied to List[Option[T]]
让我们看一下这段代码:
scala> val a = List(Some(4), None)
a: List[Option[Int]] = List(Some(4), None)
scala> a.flatMap( e=> e)
List[Int] = List(4)
为什么要在 List[Option[T]]
returns 上应用带有函数 { e => e }
的 flatMap
和删除 None
元素的 List[T]
?
具体来说,其背后的概念推理是什么——它是否基于函数式编程中的某些现有理论?这种行为在其他函数式语言中是否常见?
这虽然确实有用,但同时也让人觉得有点神奇和随意。
编辑:
感谢您的反馈和回答。
我重写了我的问题,以更加强调问题的概念性。比起 Scala 特定的实现细节,我更感兴趣的是了解它背后的正式概念。
我假设你的意思是同时支持映射和过滤 flatMap
:
scala> List(1, 2).flatMap {
| case i if i % 2 == 0 => Some(i)
| case i => None
| }
res0: List[Int] = List(2)
之所以有效,是因为 Option
的 companion object 包含从 Option[A]
到 Iterable[A]
的隐式转换,即 GenTraversableOnce[A]
,这就是 flatMap
期望作为其参数函数的 return 类型。
这是一个方便的习惯用法,但它在其他函数式语言(至少我熟悉的那些)中并不存在,因为它依赖于 Scala 的子类型、隐式转换等奇怪的组合。Haskell 例如通过 mapMaybe
for lists 提供了类似的功能。
我们先来看一下Scaladoc for Option的伴生对象。在那里我们看到一个隐式转换:
implicit def option2Iterable[A](xo: Option[A]): Iterable[A]
这意味着任何选项都可以隐式转换为 Iterable,从而产生具有零个或一个元素的集合。如果你有一个 Option[A]
而你需要一个 Iterable[A]
,编译器会为你添加转换。
在你的例子中:
val a = List(Some(4), None)
a.flatMap(e => e)
我们正在调用 List.flatMap
,它接受一个函数 A => GenTraversableOnce[B]
。在这种情况下,A
是 Option[Int]
而 B
将被推断为 Int
,因为通过隐式转换的魔力, e
在该函数中返回时将从 Option[Int]
转换为 Iterable[Int]
(GenTraversableOnce
的子类型)。
至此,我们基本上完成了以下工作:
List(List(1), Nil).flatMap(e => e)
或者,使我们的隐式显式:
List(Option(1), None).flatMap(e => e.toList)
flatMap
然后在 Option 上工作,就像它在 Scala 中对任何线性集合所做的那样:采用 A => List[B]
的函数(再次简化)并生成 List[B]
的扁平集合,取消嵌套过程中的嵌套集合。
对您的问题的简短回答是:List
类型的 flatMap
方法被定义为使用更通用的函数类型,而不仅仅是只产生 [=13] 的函数=] 结果类型。
一般结果类型为IterableOnce[B]
,如faltMap
方法签名所示:final def flatMap[B](f: (A) => IterableOnce[B]): List[B]
。 flatMap
implementation 相当简单,因为它将 f
函数应用于每个元素并在嵌套的 while
循环中迭代结果。嵌套循环的所有结果都添加到 List[B]
.
类型的结果中
因此,flatMap
适用于任何从每个列表元素生成 IterableOnce[B]
结果的函数。 IterableOnce
是一个特征,它定义了一个最小接口,它被所有可迭代的 classes 继承,包括所有集合类型(Set
、Map
等)和 Option
class。
Option
class implementation returns collection.Iterator.empty
为 None
和 collection.Iterator.single(x)
为 Some(x)
。因此 flatMap
方法会跳过 None
元素。
题目使用了identity
函数。当目的是扁平化 iterable
元素时,最好使用 flatten
方法。
scala> val a = List(Some(4), None)
a: List[Option[Int]] = List(Some(4), None)
scala> a.flatten
res0: List[Int] = List(4)
让我们看一下这段代码:
scala> val a = List(Some(4), None)
a: List[Option[Int]] = List(Some(4), None)
scala> a.flatMap( e=> e)
List[Int] = List(4)
为什么要在 List[Option[T]]
returns 上应用带有函数 { e => e }
的 flatMap
和删除 None
元素的 List[T]
?
具体来说,其背后的概念推理是什么——它是否基于函数式编程中的某些现有理论?这种行为在其他函数式语言中是否常见?
这虽然确实有用,但同时也让人觉得有点神奇和随意。
编辑:
感谢您的反馈和回答。 我重写了我的问题,以更加强调问题的概念性。比起 Scala 特定的实现细节,我更感兴趣的是了解它背后的正式概念。
我假设你的意思是同时支持映射和过滤 flatMap
:
scala> List(1, 2).flatMap {
| case i if i % 2 == 0 => Some(i)
| case i => None
| }
res0: List[Int] = List(2)
之所以有效,是因为 Option
的 companion object 包含从 Option[A]
到 Iterable[A]
的隐式转换,即 GenTraversableOnce[A]
,这就是 flatMap
期望作为其参数函数的 return 类型。
这是一个方便的习惯用法,但它在其他函数式语言(至少我熟悉的那些)中并不存在,因为它依赖于 Scala 的子类型、隐式转换等奇怪的组合。Haskell 例如通过 mapMaybe
for lists 提供了类似的功能。
我们先来看一下Scaladoc for Option的伴生对象。在那里我们看到一个隐式转换:
implicit def option2Iterable[A](xo: Option[A]): Iterable[A]
这意味着任何选项都可以隐式转换为 Iterable,从而产生具有零个或一个元素的集合。如果你有一个 Option[A]
而你需要一个 Iterable[A]
,编译器会为你添加转换。
在你的例子中:
val a = List(Some(4), None)
a.flatMap(e => e)
我们正在调用 List.flatMap
,它接受一个函数 A => GenTraversableOnce[B]
。在这种情况下,A
是 Option[Int]
而 B
将被推断为 Int
,因为通过隐式转换的魔力, e
在该函数中返回时将从 Option[Int]
转换为 Iterable[Int]
(GenTraversableOnce
的子类型)。
至此,我们基本上完成了以下工作:
List(List(1), Nil).flatMap(e => e)
或者,使我们的隐式显式:
List(Option(1), None).flatMap(e => e.toList)
flatMap
然后在 Option 上工作,就像它在 Scala 中对任何线性集合所做的那样:采用 A => List[B]
的函数(再次简化)并生成 List[B]
的扁平集合,取消嵌套过程中的嵌套集合。
对您的问题的简短回答是:List
类型的 flatMap
方法被定义为使用更通用的函数类型,而不仅仅是只产生 [=13] 的函数=] 结果类型。
一般结果类型为IterableOnce[B]
,如faltMap
方法签名所示:final def flatMap[B](f: (A) => IterableOnce[B]): List[B]
。 flatMap
implementation 相当简单,因为它将 f
函数应用于每个元素并在嵌套的 while
循环中迭代结果。嵌套循环的所有结果都添加到 List[B]
.
因此,flatMap
适用于任何从每个列表元素生成 IterableOnce[B]
结果的函数。 IterableOnce
是一个特征,它定义了一个最小接口,它被所有可迭代的 classes 继承,包括所有集合类型(Set
、Map
等)和 Option
class。
Option
class implementation returns collection.Iterator.empty
为 None
和 collection.Iterator.single(x)
为 Some(x)
。因此 flatMap
方法会跳过 None
元素。
题目使用了identity
函数。当目的是扁平化 iterable
元素时,最好使用 flatten
方法。
scala> val a = List(Some(4), None)
a: List[Option[Int]] = List(Some(4), None)
scala> a.flatten
res0: List[Int] = List(4)