Abstract: 继续阅读 Scala with Cats, 开始学习 Functor
。前面的 Monoid
和 SemiGroup
封装的是一种合并或者相加的能力,Functor
就是封装的一种进行连续操作的能力。与 Monoid
和 SemiGroup
紧密联系的操作是 fold
,而 Functor
就是 map
或者是 flatMap
。
数学背景
直观理解
如果不严格的讲,所有的带有 map
操作的都可以称之为 Functor
,所以我们熟悉的 List
,Option
,Either
都是 Functor
。
比如 List
,通过 map
操作,我们针对其中的元素进行一个链式操作,每个 map
操作后,仍然返回的是一个 List
。
1 | List(1, 2, 3). |
除了这些数据类型定义以外,对于一元方程来说也是 Functor
。
- start with
X => A
; - supply a function
A => B
; - get back
X => B
.
How does this relate to our general pattern of sequencing operations? If we think about it, function composition is sequencing. We start with a function that performs a single operation and every time we use
map
we append another operation to the chain. Callingmap
doesn’t actually run any of the operations, but if we can pass an argument to the final function all of the operations are run in sequence.
定义
Every example we’ve looked at so far is a functor: a class that encapsulates sequencing computations. Formally, a functor is a type
F[A]
with an operationmap
with type(A => B) => F[B]
.
Functor :是一个类(类型),这个类封装了一种能力,这种能力是进行连续运算。
数学定义: Functor 是一个 F[A]
类型和一个 map
操作,这个 map
操作的类型定义为 (A => B) => F[B]
。
Cat Functor 定义
1 | package cats |
Functor
的 Scala
定义十分简单,是一个带有类型参数的 trait
,同时包含一个方法 map
,map
方法定义两个类型参数 A
和 B
,其中输入的参数是类型 A 的函数 F,而输出的是类型 B 的函数 F。
这里 Functor[F[_]]
是一个高阶类型。
Functor 规范
- 如果传入的是
identity
函数,那么等价于啥都没做。 - 交换操作的顺序结果应该是一致的。
Identity: calling
map
with the identity function is the same as doing nothing:
1 | fa.map(a => a) == fa |
Composition:
mapping
with two functionsf
andg
is the same asmapping
withf
and thenmapping
withg
:
1 | fa.map(g(f(_))) == fa.map(f).map(g) |
高阶类型(Higher Kinds)和类型构造函数(Type Constructors)
Kinds
可以理解为类型的类型,直观的看就是在类型中定义的空缺的孔,或者理解为参数。通过在类型中是否有参数 把类型分为常规类型和类型构造器。
比如: List
就是一个带有一个参数的类型构造器,我们可以传入一个类型作为参数,从而将这个 List
转化为常规类型,如 List[Int]
和 List[A]
。这里虽然 List[A]
中 A
可以是任意类型,但是仍然是一个确定的参数,所以它是泛型,而不是类型构造器,List
本身才是构造器。
List // type constructor, takes one parameter
List[A] // type, produced using a type parameter
我们可以用函数和值来对等的了解上面类型的概念:
math.abs // function, takes one parameter
math.abs(x) // value, produced using a value parameter
- 类型构造器 类似于 函数
- 类型 类似于 值
Scala 使用下划线来定义类型构造器:
1 | // Declare F using underscores: |
基于这个类型构造器,我们可以看到 Cats
定义的 Functor
允许我们创建单一参数类型的类型构造器 的实例。这个实例就会具有 map
的能力。
the Cats definition of Functor allows us to create instances for any single-parameter type constructor, such as List, Option, Future, or a type alias such as MyFunc.
Cats Functors 使用
基础类型
这个用法仅仅作为理解使用,因为很多类型已经具有了 map
操作,使用 Functor
并没有太多意义。下面的例子使用的 Object
工厂方法进行 map
操作。
1 | import scala.language.higherKinds |
Lift 方法
Functor also provides the
lift
method, which converts a function of typeA => B
to one that operates over a functor and has typeF[A] => F[B]
:
这个方法称为提升,顾名思义,将原来的函数提升一个层次,提升一个阶,直观上看就是再包一层函数。
1 | val func = (x: Int) => x + 1 |
原函数是一个整型映射到整型加一,通过带有 Option
类型参数的 lift
操作,编程了 Option(整型) 映射到 Option(整型+1) 的函数。
Syntax
使用 Cats,仍然推荐使用 Syntax,代码简洁。
1 | import cats.instances.function._ // for Functor |
和 Monoid
不同,这里没有定义操作符 (如 |+|
),而是直接使用 map
。这里一定要区分开那些是类型原生的 map
方法,那些是 Functor
提供的 map
。
自定义 Functor 实例
参考 Type Class 模式,我们同样可以自定义任意类型的 Functor
,只需要实现其 map
操作。比如我们自己定义一个 Future
的 Functor
1 | import scala.concurrent.{Future, ExecutionContext} |
案例: 二叉树
1 | sealed trait Tree[+A] |
逆变和不变
总结
Functor 表现的是一系列连续的操作。主要有三种类型
- 常规协变 Functor
- 逆变 Functor
- 不变 Functor