Scala with Cats 学习笔记4-Functor

Abstract: 继续阅读 Scala with Cats, 开始学习 Functor。前面的 MonoidSemiGroup 封装的是一种合并或者相加的能力,Functor 就是封装的一种进行连续操作的能力。与 MonoidSemiGroup 紧密联系的操作是 fold,而 Functor 就是 map 或者是 flatMap

数学背景

直观理解

如果不严格的讲,所有的带有 map 操作的都可以称之为 Functor,所以我们熟悉的 ListOptionEither 都是 Functor
比如 List,通过 map 操作,我们针对其中的元素进行一个链式操作,每个 map 操作后,仍然返回的是一个 List

1
2
3
4
List(1, 2, 3).
map(n => n + 1).
map(n => n * 2).
map(n => n + "!")

除了这些数据类型定义以外,对于一元方程来说也是 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. Calling map 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 operation map with type (A => B) => F[B].

Functor :是一个类(类型),这个类封装了一种能力,这种能力是进行连续运算。

数学定义: Functor 是一个 F[A] 类型和一个 map 操作,这个 map操作的类型定义为 (A => B) => F[B]

Type chart: generalised functor map

Cat Functor 定义

1
2
3
4
5
6
7
package cats

import scala.language.higherKinds

trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}

FunctorScala 定义十分简单,是一个带有类型参数的 trait,同时包含一个方法 mapmap 方法定义两个类型参数 AB,其中输入的参数是类型 A函数 F,而输出的是类型 B函数 F

这里 Functor[F[_]] 是一个高阶类型。

Functor 规范

  1. 如果传入的是 identity 函数,那么等价于啥都没做。
  2. 交换操作的顺序结果应该是一致的。

Identity: calling map with the identity function is the same as doing nothing:

1
fa.map(a => a) == fa

Composition: mapping with two functions f and g is the same as mapping with f and then mapping with g:

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

  1. 类型构造器 类似于 函数
  2. 类型 类似于

Scala 使用下划线来定义类型构造器

1
2
3
4
5
6
7
8
// Declare F using underscores:
def myMethod[F[_]] = {

// Reference F without underscores:
val functor = Functor.apply[F]

// ...
}

基于这个类型构造器,我们可以看到 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import scala.language.higherKinds
import cats.Functor
import cats.instances.list._ // for Functor
import cats.instances.option._ // for Functor

val list1 = List(1, 2, 3)
// list1: List[Int] = List(1, 2, 3)

// 这里 list1 作为一个参数存入了 Functor 的 map 方法
val list2 = Functor[List].map(list1)(_ * 2)
// list2: List[Int] = List(2, 4, 6)

val option1 = Option(123)
// option1: Option[Int] = Some(123)

val option2 = Functor[Option].map(option1)(_.toString)
// option2: Option[String] = Some(123)

Lift 方法

Functor also provides the lift method, which converts a function of type A => B to one that operates over a functor and has type F[A] => F[B]:

这个方法称为提升,顾名思义,将原来的函数提升一个层次,提升一个阶,直观上看就是再包一层函数。

1
2
3
4
5
6
7
8
val func = (x: Int) => x + 1
// func: Int => Int = <function1>

val liftedFunc = Functor[Option].lift(func)
// liftedFunc: Option[Int] => Option[Int] = cats.Functor$$Lambda$11699/[email protected]

liftedFunc(Option(1))
// res0: Option[Int] = Some(2)

原函数是一个整型映射到整型加一,通过带有 Option 类型参数的 lift 操作,编程了 Option(整型) 映射到 Option(整型+1) 的函数。

Syntax

使用 Cats,仍然推荐使用 Syntax,代码简洁。

1
2
3
4
5
6
7
8
9
import cats.instances.function._ // for Functor
import cats.syntax.functor._ // for map

val func1 = (a: Int) => a + 1
val func2 = (a: Int) => a * 2
val func3 = (a: Int) => a + "!"
val func4 = func1.map(func2).map(func3)

func4(123)

Monoid 不同,这里没有定义操作符 (如 |+|),而是直接使用 map。这里一定要区分开那些是类型原生的 map 方法,那些是 Functor 提供的 map

自定义 Functor 实例

参考 Type Class 模式,我们同样可以自定义任意类型的 Functor,只需要实现其 map 操作。比如我们自己定义一个 FutureFunctor

1
2
3
4
5
6
7
8
import scala.concurrent.{Future, ExecutionContext}

implicit def futureFunctor
(implicit ec: ExecutionContext): Functor[Future] =
new Functor[Future] {
def map[A, B](value: Future[A])(func: A => B): Future[B] =
value.map(func)
}

案例: 二叉树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
sealed trait Tree[+A]

final case class Branch[A](left: Tree[A], right: Tree[A])
extends Tree[A]

final case class Leaf[A](value: A) extends Tree[A]

import cats.Functor

implicit val treeFunctor: Functor[Tree] =
new Functor[Tree] {
def map[A, B](tree: Tree[A])(func: A => B): Tree[B] =
tree match {
case Branch(left, right) =>
Branch(map(left)(func), map(right)(func))
case Leaf(value) =>
Leaf(func(value))
}
}

object Tree {
def branch[A](left: Tree[A], right: Tree[A]): Tree[A] =
Branch(left, right)

def leaf[A](value: A): Tree[A] =
Leaf(value)
}

Branch(Leaf(10), Leaf(20)).map(_ * 2)
Tree.leaf(100).map(_ * 2)
// res10: wrapper.Tree[Int] = Leaf(200)
Tree.branch(Tree.leaf(10), Tree.leaf(20)).map(_ * 2)
// res11: wrapper.Tree[Int] = Branch(Leaf(20),Leaf(40))

逆变和不变

总结

Functor 表现的是一系列连续的操作。主要有三种类型

  1. 常规协变 Functor
  2. 逆变 Functor
  3. 不变 Functor