Scala: flatMap with tuples
以下语句为什么对.map()有效,但对.flatMap()无效?
1 2 3 4
| 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)) |
该语句没有问题,但是:
1
| val tupled = input. flatMap(x => List (x *2, x *3)) |
- input是什么类型?检查map和flatMap方法签名。它具有不同的参数,这就是代码有编译错误的原因。
-
因为flatMap用于展平结构。当您使用map获得M[M[A]]时,您将使用flatMap获得M[A]。
-
嵌套的M有时可以不同,N。例如,List[Option[String]] => List[String]
-
事情是a.flatMap(a => b)可以用a.map(a => b).flatten粗略地近似。为了使flatMap正常工作,函数的返回类型应该是monad或可以展平的东西。
假设input如果是List[Int]类型,则map接受从Int到A的功能,而flatMap接受从Int到List[A]的功能。
根据您的用例,您可以选择其中一种,但是它们绝对不能互换。
例如,如果仅转换List的元素,则通常需要使用map:
1
| List(1, 2, 3).map(x => x * 2) // List(2, 4, 6) |
,但是您想更改List的结构,例如-将每个元素"分解"到另一个列表中,然后将它们展平,flatMap是您的朋友:
1
| 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]的函数。
1 2 3 4 5 6 7 8 9 10
| 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实现中提取的:
1
| final override def flatMap [B, That ](f : scala. Function1[A, scala. collection. GenTraversableOnce[B ]])(... ) |
这有点难解压,我已经删除了其中的一些,但是关键是GenTraversableOnce类型。 List,如果您遵循它的继承链,则将其作为其构建的特征,因此可以将某个类型从A映射到List(或任何带有GenTraversableOnce特征)将是有效的函数。显然,元组不具有此特征。
这是杂草解释为何键入错误的原因,并且值得解释,因为任何提示"无法使用此类签名解析引用"的错误都意味着无法找到需要该签名的函数您提供的显式类型。类型通常是在Scala中进行推断的,因此可以确保使用的类型是调用的方法所期望的类型。
请注意,flatMap在函数式编程中具有标准含义,即大致来说,任何消耗单个元素并生成n个元素的映射函数,但最终结果是所有这些列表的串联。因此,传递给flatMap的函数将始终期望产生一个列表,并且没有flatMap函数将期望知道如何对单个元素起作用。