胡扯flatMap in Scala
初识flatMap记得还是学Spark的时候,看到RDD有这么个接口函数,当时只觉得其功能和map有些像(把list的每个元素map成一个list,再把这些list合并),其名字也和这个功能很符合,其他就没什么概念了。之后,走马观花地学习了零零星星的其他FP相关的东西,又经常发现这个函数。再往后才慢慢知道这是个什么东西——flatMap没有我想象的那么普遍,也没有我想象的那么局限。简单讲一讲我对它的理解。
画外音:学过Monad的人都知道行内有一条规矩,那就是不要在网上发表自己的Monad教程,防止误导别人。。此文只是我一个菜鸟的粗浅理解,不是教程,但是此处还是应该有
Disclaimer:(如果你不知道什么是Monad,建议先去学习权威的教程) x 3,重要的事情说三遍。
flatMap没有想象的那么普遍:在Google或百度上搜,flatMap相关的网页大部分都是Scala或者Spark相关的内容。我却以为和map一样应该是个普遍的概念。虽然这个概念在其他FP里面也有涉及,但似乎发扬大的还是在Scala里(或者相同的概念在其他语言里叫其他名字或者用某某符号代替了)。
flatMap没有想象的那么局限:最开始以为list才有flatMap,其实不止list可以,Try,Future等等很多其他类型也可以进行flatMap操作。而支持flatMap的类型也被抽象出来。支持flatMap操作的类型被称为广义上的Monad(狭义的Monad要遵循Monad要满足Monad三定律)。
不过,其他Monad上的flatMap操作内容就没有list那么直接体现出flat的含义了。举个Future的例子:
val respFuture: Future[HttpResponse] = httpClient.get("http://baidu.com")
val statFuture: Future[HttpResponse] = respFuture.flatMap(resp => httpClient.post(s"http://stat.com?len=${resp.body.length}"))
这个例子里,我们先get到baidu.com的网页,然后把response正文的长度post到一个叫stat.com的网站上去。这里假定httpClient的get和post都是返回future。注意第二行为什么是用flatMap。因为可以考虑下,如果把它换成map方法,那么statFuture的类型会是Future[Future[HttpResponse]],这与我们意图是不符的。实际上Future#flatMap直接把接收的参数的返回值(这个参数本身是个函数,所以有返回值)返回了,相当于“脱了一层壳”,返回的是Future[HttpResponse]。
了解了Future#flatMap的意义,我们把它和List#flatMap对比一下,可以发现这两个方法的作用也不是那么相近对吧?因为毕竟Future和List这两个东西就很不一样。。那他们为啥都叫flatMap呢?然后下面我就开始胡扯了。
可以发现Future和List都有着一种同功能——作为“容器”:Future“包裹”着未来可能的完成的结果(当然实际上以callback来实现);List“包裹”着若干个元素。也就是说他们首先都要是支持泛型的类型。既然支持泛型,那么根据包裹类型的不同Future和List都能有相应的不同类型,比如Future[String]和Future[Int], List[String]和List[Int]。我们经常会有给定一个Future[String]得到一个Future[Int],或者给定一个List[String]得到一个List[Int]的需求,而map和flatMap就是专门干这事的:
把一个泛型对象转换成同一泛型的另一对象
但是map和flatMap的实现方式和使用方式都不同,下面祭出我自己的理解:
M[*]就是泛型,其实就是Monad啦!红色箭头表示你需要传给map方法的函数;蓝色箭头表示你需要传给flatMap方法的函数。而泛型类根据其自己的map和flatMap的实现来决定如何使用你传给他的这些函数。
这里就很有意思了,我们接触到的List#flatMap和Future#flatMap的实现都是广为大众熟知的实现。但这并不代表他们的实现的功能有什么共同点,只是他们的签名具有很强的一致性罢了。实际上我也可以把List的flatMap实现定义成:把原List每个元素map成的List各自里面的最大值拼成一个新的List作为结果。这种奇怪的定义完全符合flatMap的要求,但是没什么卵用罢了,所以他不可能是默认的内置实现。
相比而言map方法似乎就没有这样的发挥空间了,至少对于Future和List而言,直观感觉上map的实现都应该是deterministic的。
好像胡扯完了,里面包含了太多主观的臆想,肯定有不少错误。还希望好心人能够帮我指正出来,谢谢!