什么是Functor?
先看一段代码:
1 | // js |
这些类型都有map
,而且看上去map
的作用好像都相同。
事实上,它们确实相同。
不那么准确地说,任何东西只要有map
,我们就可以将它视作functor。
要研究functor,我们需要转变一下(命令式)思路。
可以将functor想象成一个容器,容器里放了一些元素。
map
并不是对容器进行一次遍历(traverse),而是对容器内的元素做一个变换(transform)。
如果有多个map
被串起来了,则会按照先后顺序,进行变换。(这里顺序是很重要的,下面会细说)
Functor的定义
我们可以将functor定义为一个trait:
1 | trait Functor[F[_]] { |
对于一个数据结构,我们只需要让其继承这个trait,并实现map
,就可以把它当作functor来用了。
比如:
1 | val listFunctor: Functor[List] = new Functor[List] { |
functor法则是不言自明的:
1 | map(s)(a => a) == s |
即,用identity函数映射一个结构s,结果仍然是s。
观察一下,我们可以发现functor能做的事仅有:
- 将容器内的元素进行变换(transform)
除此之外,都不可以。
比如这些事情,functor都是做不到的:
- 改变容器本身:
List
变成一个Set
,Some
变成一个None
; - 将元素取出来:Functor[A] 变成 A;
- 改变元素:移除
List
的第一个元素; - 将元素聚合成一个:比如sum起来;
- 抛出异常。
非常规functor的例子
Future
Future
也有map
方法,也是一个functor 。
map
会将异步计算应用于Future
内的元素,多个map
会按照顺序先后执行。
1 | import scala.concurrent.{Await, Future} |
当我们使用Future
的时候,其实我们并不清楚Future
内部状态。
-
如果
Future
已经完成了,那么我们的map
就会立刻被调用。 -
如果
Future
尚未完成,我们的map
会被放到默认的ExecutionContext
里的队列中,等着,之后再执行。
对于Future
上的map
,我们并不知道map
何时会执行,我们只知道map
执行的顺序。
Function(别急,先收起你头上的???)
考虑这种情况,我们有一个f: T => A
和一个p: A => B
,想要得到一个g: T => B
。
你一定会说,这个简单,基本的function composition嘛。
这里我们需要换一个视角:
- 将
T => A
看作SomeFunc[A]
- 给
SomeFunc[A]
一个A => B
- 得到一个
SomeFunc[B]
怎么样,我们是不是对SomeFunc
进行了一次map
。
看一段例子,这里我们用到了cats这个库,cats为Scala提供了很多函数式的抽象。
1 | import cats.instances.function._ |
当我们写下val f = f1.map(f2).map(f3).map(f4)
的时候,这一串map
就是将一系列的计算’串’在了一起,等到f
被调用的时候再执行(和Future
一样)。
PS: 这个我们其实叫它Reader Functor(详细内容可以看references中第一篇)。
小结
functor是FP中非常非常基础的抽象,就像monoid一样,看上去对我们日常的编程并没有太大的作用(其实我们可以根据functor构建通用的zip
和unzip
)。不过没关系,之后我们还会研究一些functor的特例,monad和applicative functor,这些是十分实用且普遍的。
PS:你可以在范畴论中找到functor的原型,functor是范畴的态射(morphism of categories),会将一个范畴中的morphism和object映射到另一个范畴中。