一、 前言

  1. 我在学习一个知识点时,还是习惯性以问题解决为出发点去学习。首先先抛出一个问题,看自己是否能解决或者回答正确,检测自己知识的掌握熟悉程度和找出自己的知识盲点,针对性去学习。
  2. 在实际开发过程中,或许多多少少有使用函子去解决问题。但是因为之前没有系统性的学习过函数式编程,大脑里并没有意识到自己代码里所做的那些处理就是所谓的函子。

二、例子

讲到函子,我们首先回到我们的问题上来。之前我们执行函数(以加减乘除运算为例)通常如下:

  1. function double(x) {
  2. return x * 2
  3. }
  4. function add5(x) {
  5. return x + 5
  6. }
  7. double(add5(1))
  8. //或者
  9. var a = add5(5)
  10. double(a)

那现在我们想以数据为核心,一个动作一个动作去执行。

  1. (5).add5().double()

很显然,这种以数据为出发点的链式调用,更具有可读性和维护性,运算的顺序可以任意调换。不需要从一堆嵌套的函数找出目标函数去调换到其他位置,或者增加一堆不必要的临时变量。

实现这样的调用,需要满足的条件又两个:

  • (5)必须是一个引用类型,因为需要挂载方法。
  • 引用类型上要有可以调用的方法

首先,我们试着去创建一个引用类型。

  1. class Num{
  2. constructor (value) {
  3. this.value = value ;
  4. }
  5. add5(){
  6. return this.value + 5
  7. }
  8. double(){
  9. return this.value * 2
  10. }
  11. }
  12. var num = new Num(5);
  13. num.add5();

但是上面的例子存在如下问题:

  • 经过调用后,返回的就是一个值了,我们没有办法进行下一步处理。
  • 我们需要它返回一个对象。

    1. class Num{
    2. constructor (value) {
    3. this.value = value ;
    4. }
    5. add5 () {
    6. return new Num( this.value + 5)
    7. }
    8. double () {
    9. return new Num( this.value * 2)
    10. }
    11. }
    12. var num = new Num(2);
    13. num.add5 ().double ()
  • 我们通过new Num ,创建了一个num 一样类型的实例

  • 把处理的值,作为参数传了进去从而改变了this.value的值
  • 我们把这个对象返了回去,可以继续调用方法去处理函数

我们发现,new Num( this.value + 5),中对this.value的处理,完全可以通过传进去一个函数去处理。
并且在真实情况中,我们也不可能为每个实例都创建这样有不同方法的构造函数,它们需要一个统一的方法。

  1. class Num{
  2. constructor (value) {
  3. this.value = value ;
  4. }
  5. map (fn) {
  6. return new Num(fn(this.value))
  7. }
  8. }
  9. var num = new Num(2);
  10. num.map(add5).map(double)

我们创建了一个map的方法,把处理的函数fn传了进去。这样我们就完美的实现了我们设想的功能啦。
最后重新整理下这个函数:

  1. class Functor{
  2. static of(val){
  3. return new Functor(val);
  4. }
  5. constructor (value) {
  6. this.value = value ;
  7. }
  8. map (fn) {
  9. return new Functor.of(fn(this.value))
  10. }
  11. }
  12. Functor.of(5).map(add5).map(double)
  • 我们把原来的构造函数Num的名字改成了Functor
  • 添加了一个of 静态方法, 可以省略new关键字去创建对象


    现在使用Functor.of(5).map(add5).map(double)去调用函数, 有没有觉得很简洁清爽。

    其实,在这个一路遇到问题,解决问题的过程中,我们已经是不知不觉的把函子的概念学习完了。上面的例子上面这个例子总的Functor就是函子。现在我们来总结一下,它有那些特点吧。

  • Functor是一个容器,它包含了值,就是this.value.(想一想你最开始的new Num(5))

  • Functor具有map方法。该方法将容器里面的值,映射到另一个容器。(想一想你在里面是不是new Num(fn(this.value))
  • 函数式编程里面的运算,都是通过函子完成,即运算不直接针对值,而是针对这个值的容器——函子。(想一想你是不是没直接去操作值)
  • 函子本身具有对外接口(map方法),各种函数就是运算符,通过接口接入容器,引发容器里面的值的变形。(说的就是你传进去那个函数把this.value给处理啦)
  • 函数式编程一般约定,函子有一个of方法,用来生成新的容器。(就是最后咱们整理了一下函数嘛)

三、 什么是函子(Functor)

容器:处理值与值之间的变形关系(这个变形关系就是函数)
函子: 它是一个特殊的容器,通过一个普通对象来实现,该对象具有map方法,map方法方法可以运行一个函数对值进行处理(变形关系)
容器:包含值和值的变形关系(这个变形关系就是函数)

  1. // Functor 函子
  2. // 函子是一个普通的对象,这个对象里面应该维护一个值,并且要对外公布一个map方法,所以我们可以通过一个类来描述函子
  3. // 先定义一个class
  4. // 函子是一个容器,所以这个类的名字叫Container
  5. class Container {
  6. // 这个类中先定义一个构造函数
  7. // 当我们创建函子的时候,函子内部要有一个值,所以在构造函数中,要把这个值传进来
  8. constructor (value) {
  9. // 函子内部要把这个值存储起来
  10. // 注意:这个值是函子内部维护,只有它自己知道,这个值永远不对外公布
  11. // 所以在定义这个属性的时候,可以通过this._value定义这个属性(约定所有以下划线开头的这些成员,都是私有的成员,也就是这个value不想让外部访问)
  12. // 这个值就等于初始化的时候传递进来的值
  13. this._value = value
  14. }
  15. // 还要对外公布一个map方法
  16. // map方法的作用是接收一个处理值的函数,这个函数是一个纯函数,以为需要把value传递给这个函数,由这个函数真正处理这个值
  17. map (fn) {
  18. // map返回的不是值,而是一个新的函子对象,在这个新的函子对象里保存新的值,始终不把值对外公布,想要处理值的话,就要给map对象传递一个处理值的函数
  19. // 在返回这个新的函子的时候,需要把处理的值传递给Container
  20. return new Container(fn(this._value))
  21. }
  22. }
  1. // 每次创建Container的时候都需要调用new实现,这种是面向对象编程,现在是使用函数式编程,所以可以进行修改一下
  2. class Container {
  3. // 创建一个静态的方法of,of的作用就是返回一个函子的对象,在创建函子对象的时候,of需要接收一个参数value, of放方法中直接返回new Container,其实of方法中就封装了new关键字
  4. static of (value) {
  5. return new Container(value)
  6. }
  7. constructor (value) {
  8. this._value = value
  9. }
  10. map (fn) {
  11. // 在map方法中,可以使用of方法,因为of是一个静态方法,可以直接通过类名调用
  12. return Container.of(fn(this._value))
  13. }
  14. }
  15. let r = Container.of(5)
  16. .map(x => x + 2)
  17. .map(x => x * x)
  18. console.log(r);