我们来看看这段代码:
scala> val a = List(Some(4), None)
a: List[Option[Int]] = List(Some(4), None)
scala> a.flatMap( e=> e)
List[Int] = List(4)
为什么在flatMap
上使用{ e => e }
函数List[Option[T]]
会返回List[T]
并移除None
个元素?
具体来说,它背后的概念推理是什么?它是基于函数式编程中的一些现有理论吗?这种行为在其他函数式语言中是否常见?
虽然这确实很有用,但同时确实感觉有点神奇而又随意。
编辑:
感谢您的反馈和回答。 我重写了我的问题,更加强调问题的概念性。而不是Scala特定的实现细节,我更感兴趣的是了解它背后的正式概念。
答案 0 :(得分:7)
我认为您的意思是使用flatMap
同时支持映射和过滤:
scala> List(1, 2).flatMap {
| case i if i % 2 == 0 => Some(i)
| case i => None
| }
res0: List[Int] = List(2)
这是有效的,因为Option
的{{3}}包含从Option[A]
到Iterable[A]
的隐式转换,即GenTraversableOnce[A]
,这是{{1}期望作为其参数函数的返回类型。
这是一个方便的习惯用法,但它并不存在于其他函数式语言中(至少是我熟悉的),因为它依赖于Scala奇怪的子类型,隐式转换等组合。例如Haskell提供但是,通过companion object的类似功能。
答案 1 :(得分:4)
让我们首先看一下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
然后对选项进行处理,就像它对Scala中的任何线性集合一样:采用A => List[B]
的函数(再次,简化)并生成List[B]
的扁平集合,将嵌套集合嵌套在流程中。
答案 2 :(得分:0)
一个简单的答案是:flatMap
类型的List
方法被定义为与更通用的函数类型一起使用,而不仅仅是仅产生List[B]
的函数。结果类型。
一般结果类型为IterableOnce[B]
,如faltMap
方法签名:final def flatMap[B](f: (A) => IterableOnce[B]): List[B]
中所示。 flatMap
implementation非常简单,因为它将f
函数应用于每个元素,并在嵌套的while
循环中迭代结果。嵌套循环中的所有结果都将添加到List[B]
类型的结果中。
因此,flatMap
可以与从每个列表元素生成IterableOnce[B]
结果的任何函数一起使用。 IterableOnce
是一个特性,它定义了一个最小接口,该接口被所有可迭代类(包括所有集合类型(Set
,Map
等)和Option
类继承。
Option
class implementation为collection.Iterator.empty
返回None
,为collection.Iterator.single(x)
返回Some(x)
。因此,flatMap
方法将跳过None
元素。
该问题使用identity
函数。当目的是平整flatten
元素时,最好使用iterable
方法。
scala> val a = List(Some(4), None)
a: List[Option[Int]] = List(Some(4), None)
scala> a.flatten
res0: List[Int] = List(4)