# 函数式编程

函数式编程起源很早,最近随着 React 的热度逐渐被更多人关注,这一章我们来介绍一下。

# 函数式编程起源

  • 函数式编程的起源,是一门叫做范畴论(Category Theory)的数学分支。
  • 彼此之间存在某种关系的概念、事物、对象等等,都构成"范畴"。随便什么东西,只要能找出它们之间的关系,就能定义一个"范畴"。
  • 箭头表示范畴成员之间的关系,正式的名称叫做"态射"(morphism)。范畴论认为,同一个范畴的所有成员,就是不同状态的"变形"(transformation)。通过"态射",一个成员可以变形成另一个成员。

# 函数式编程概念

  • 函数式编程,其实相对于计算机的历史而言是一个非常古老的概念,甚至早于第一台计算机的诞生。
  • 函数式编程不是用函数来编程,也不是传统的面向对象编程。主旨在于将复杂的函数复合成简单的函数。运算过程尽量写成一系列嵌套调用。
  • JavaScript 是披着 C 外衣的 Lisp。
  • 真正火热是随着 React 的高阶函数而逐步升温。

# 纯函数

  • 对于相同的输入,永远得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。
  • Array.clice 是纯函数,因为它没有副作用,对于固定的输入,输出总是固定的。

# 柯里化

传递给函数一部分参数来调用它,让它返回一个函数区处理剩下的参数。

var checkage=>min=>(age=>age>min)
var checkage20=checkage(20)
checkage20(100)
1
2
3

# 函数组合

解决类似的函数嵌套问题 f(h(j(k()))) 。定义一个组合函数来讲多个函数调用组合成一个。

var compose=(f,g)=>(x=>f(g(x)));
1

# 声明式与命令式代码

  • 命令式代码:通过编写一条又一条的指令,去让计算机执行一些动作,这其中一半都会涉及到很多繁杂的细节。
  • 声明式代码:通过写表达式的方法来表明我们想干什么,问不是通过一步步的指示。

# 惰性函数

惰性函数式 “比较懒的函数”,只执行一次就不执行了,是因为缓存了上一次的结果,直接拿来用。

# 高阶函数

将函数当参数,把传入的函数做一个封装,然后返回这个封装函数,达到更高程度的抽象。

# 闭包

虽然外部 a 执行完毕,栈上的帧被释放,但是堆上的作用域并不能被释放,因此 x 依旧可以被外部函数访问,这样就形成的闭包。

function a(x) {
  return function(y) {
    return x + y;
  };
}
var a1 = a(1);
a1(3); //4
1
2
3
4
5
6
7

# 函子

  • 函数不仅可以用于同一个范畴中值得转换,还可以用于将一个范畴转换成另一个范畴。这就涉及到了函子(Functor)。
  • 函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。
  • 函子是一种范畴,也就是说,是一个容器,它包含了值和变形关系。比较特殊的是,它的变形关系可以一次作用于每一个值,将当前容器变形成另一个容器。
class Functor {
  constructor(val) {
    this.val = val;
  }

  map(f) {
    return Functor.of(f(this.val));
  }
}

Functor.of = function(value) {
  return new this(value);
};
1
2
3
4
5
6
7
8
9
10
11
12
13

# Maybe 函子

函子接受各种函数,处理容器内部的值。这里就有一个问题,容器内部的值可能是一个空值(比如 null),而外部函数未必有处理空值的机制,如果传入空值,很可能就会出错。

Maybe 函子就是为了解决这一类问题而设计的。简单说,它的 map 方法里面设置了空值检查。

class Maybe extends Functor {
  constructor(value) {
    super();
    this.val = value;
  }
  isnothing() {
    return !!!this.val;
  }

  map(f) {
    if (this.isnothing()) {
      return Maybe.of(null);
    } else {
      return Maybe.of(f(this.val));
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Either 函子

条件运算 if...else 是最常见的运算之一,函数式编程里面,使用 Either 函子表达。

Either 函子内部有两个值:左值(Left)和右值(Right)。右值是正常情况下使用的值,左值是右值不存在时使用的默认值。

class Either extends Functor {
  constructor(value) {
    super();
    this.val = value;
  }
  isnothing() {
    return !!!this.val;
  }
  map(left, right) {
    if (this.isnothing()) {
      return Either.of(left(null));
    } else {
      return Either.of(right(this.val));
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# AP 函子

AP 函子的意义在于,对于那些多参数的函数,就可以从多个容器之中取值,实现函子的链式操作。

class Ap extends Functor {
  constructor(value) {
    super();
    this.val = value;
  }
  ap(F) {
    return Ap.of(this.val(F.val));
  }
}
1
2
3
4
5
6
7
8
9

# Monad 函子

Monad 函子的作用是,总是返回一个单层的函子。它有一个 flatMap 方法,与 map 方法作用相同,唯一的区别是如果生成了一个嵌套函子,它会取出后者内部的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。

class Monad extends Functor {
  constructor(value) {
    super();
    this.val = value;
  }
  join() {
    return this.val;
  }
  map(f) {
    return Monad.of(f(this.val)).join();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

# IO 函子

Monad 函子的重要应用,就是实现 I/O (输入输出)操作。

class IO extends Functor {
  constructor(value) {
    super();
    this.val = value;
  }
  flatMap() {
    return this.val;
  }
  map(f) {
    return IO.of(f(this.val));
  }
}
var a = IO.of(2);

function readOne() {
  return IO.of(function() {
    window.aaa = 100;
  });
}
readOne().flatMap()();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 常用的函数式编程的库

  • RxJS
  • CycleJS
  • LoadshJS
  • UnderscoreJS
  • RamdaJS

# 实际运用场景

  • 易调试(无副作用,同样的输入一定是同样的输出)
  • 单元测试

# 小结

  • 1、面向过程编程:想到哪写到哪。
  • 2、面向对象编程:共有的属性和方法封装到一个类里(封装 继承 多态)。
  • 3、面向切面编程:无侵入式的扩展,例如:统计一个函数的执行时间。
  • 4、函数式编程:提纯无关业务的纯函数,函数套函数产生神奇的效果。
  • 5、函数式编程不是用函数来编程,函数套函数让函数更强大。
  • 6、javascript 函数称为一等公民。
  • 7、对于函数式编程来讲,同样的输入一定会有同样的输出,永远不依赖外部的状态。
  • 8、纯函数可以记忆(同样的输入一定会有同样的输出),不跟外界有任何关系,抽象代码方便单元测试。
  • 9、函数式编程新建一个独立的 js ,通过代码的提纯,新建共有的 common.js ->libs 工具库。
  • 10、函数柯里化:函数接收一堆参数,返回一个新函数,用来继续接收参数,处理业务逻辑。
  • 11、函数柯里化:它可以记住参数,相当于是对参数的一种缓存。
  • 12、函数组合是为了解决柯里化函数所最后生成的洋葱式的代码。
  • 14、把一些对象自带的方法转化成纯函数,不要生成转瞬即逝的中间变量。
  • 15、声明式的代码越来越受欢迎,只要提供一条条的指令,程序就自己知道该怎么做,而不是一步一步地告诉高旭应该这么做。
  • 16、惰性函数就是比较懒的函数,下一次就不想再求值了(将上一次的运行结果缓存起来了)。
  • 17、高阶函数:函数传给函数,让函数具有更复杂的能力和功能。
  • 18、尾递归的性能要高于传统纯函数递归的性能。
  • 19、函数式编程其实就是函数的种种核心技巧的拼接,但是函数是变成会充盈着大量的闭包,闭包是 js 中常见的核心知识。
  • 20、常用的函数式编程的库:RxJS,CycleJS,LoadshJS,UnderscoreJS,RamdaJS。
  • 21、抽象业务逻辑,系统里有很多可以复用,组合起来有更强大的功能的时候,抽库,增加代码健壮性,方便单元测试。
  • 22、函数式编程的唯一影响因素就是输入和输出。
  • 23、解决多线程死锁问题,每一个函数式编程里面,根本不设计到外部的那个被几个线程争执的变量。
  • 24、范畴代表一个容器,容器内部有两种因素组成,一个 value ,一个变形关系。
  • 25、变形关系也是函数,这个变形关系只能作用于单独的该容器下的一个元素,但是范畴和范畴之间可以相互转化,用到的也是变形函数,但是这个变形函数非常特殊,因为它能作用于当前容器的每一个元素,他有一个特殊的名字——函子,这些函子本身都是数学方法,后来被依次利用到函数式编程中。
  • 26、本来一个函数不可以调用自身的函数,函子可以,容器只留出一个接口 map ,可以运行容器内的函数。
  • 27、一个容器一旦接受了变形关系(接受一个函子),就可以变成一个新的容器。
  • 28、加一层容错机制 Maybe ,处理不纯的函数 IO 。

# 参考链接

函数式编程入门教程

Last Updated: 7/19/2019, 11:39:19 PM