为什么要『函数式编程』?
范畴论
函数式编程的起源,是一门叫做范畴论(Category Theory)的数学分支,它认为世界上所有的概念体系,都可以抽象成一个个的”范畴”(category)。
范畴的概念
彼此之间存在某种关系的概念、事物、对象等等,都构成”范畴”。随便什么东西,只要能找出它们之间的关系,就能定义一个”范畴”。
范畴论认为,同一个范畴的所有成员,就是不同状态的”变形”(transformation)。通过”态射”,一个成员可以变形成另一个成员。
数学模型
- 所有成员是一个集合
- 变形关系是函数
也就是说,范畴论是集合论更上层的抽象,简单的理解就是”集合 + 函数”。
理论上通过函数,就可以从范畴的一个成员,算出其他所有成员。
范畴与容器
我们可以把”范畴”想象成是一个容器,里面包含两样东西。
- 值(value)
- 值的变形关系,也就是函数。
下面我们使用代码,定义一个简单的范畴。
1 | class Category { |
上面代码中,Category
是一个类,也是一个容器,里面包含一个值(this.val
)和一种变形关系(addOne
)。你可能已经看出来了,这里的范畴,就是所有彼此之间相差1的数字。
范畴论与函数式编程的关系
伴随着范畴论的发展,就发展出一整套函数的运算方法。这套方法起初只用于数学运算,后来有人将它在计算机上实现了,就变成了今天的”函数式编程”。
在函数式编程中,函数就是一个管道(pipe)。这头进去一个值,那头就会出来一个新的值,没有其他作用。
函数的合成与柯里化
函数式编程有两个最基本的运算:合成和柯里化。
函数的合成
如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做”函数的合成”(compose)。
函数的合成必须满足结合律。
1 | compose(f, compose(g, h)) |
柯里化
f(x)
和g(x)
合成为f(g(x))
,有一个隐藏的前提,就是f
和g
都只能接受一个参数。如果可以接受多个参数,比如f(x, y)
和g(a, b, c)
,函数合成就非常麻烦。
这时就需要函数柯里化了。所谓”柯里化”,就是把一个多参数的函数,转化为单参数函数。
1 | // 柯里化之前 |
有了柯里化以后,我们就能做到,所有函数只接受一个参数。
函子
函数不仅可以用于同一个范畴之中值的转换,还可以用于将一个范畴转成另一个范畴。这就涉及到了函子(Functor)。
函子的概念
函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。
它首先是一种范畴,也就是说,是一个容器,包含了值和变形关系。比较特殊的是,它的变形关系可以依次作用于每一个值,将当前容器变形成另一个容器。
函子的代码实现
任何具有map
方法的数据结构,都可以当作函子的实现。
1 | class Functor { |
上面代码中,Functor
是一个函子,它的map
方法接受函数f作为参数,然后返回一个新的函子,里面包含的值是被f
处理过的(f(this.val)
)。
一般约定,函子的标志就是容器具有map
方法。该方法将容器里面的每一个值,映射到另一个容器。
学习函数式编程,实际上就是学习函子的各种运算。