函数式编程 1
webkong
# 函数式编程 1
"函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。
# 一、函数式编程的概念
主要思想是把运算过程尽量写成一系列嵌套的函数调用,用数学的方式编程
随着 react 高阶函数(HOC)而火热(高阶组件简单来说就是一个组件传给另一个组件,然后返回一个新的组件) 例如:最常用的就是
map
和reduce
- 函数是"第一等公民":函数与其他数据类型一样,可以赋值给其他变量,也可以作为参数
- 不修改状态:这里的变量在函数式编程中被函数代替了,仅仅代表某个表达式,这里的变量是不能被修改的,需要什么就 return 出去
- 只用"表达式",不用"语句":函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。
- 没有"副作用":所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。
- 引用透明:指的是函数的运行不依赖于外部变量或"状态",相同输入总是相同输出
# 二、纯函数
相同的输入,永远得到相同的输出,而且没有任何可观测的副作用,也不依赖外部环境
// Array.slice()就是纯函数
let arr = [1, 2, 3, 4, 5];
arr.slice(0, 3); //[1, 2, 3]
arr.slice(0, 3); //[1, 2, 3]
arr.slice(0, 3); //[1, 2, 3]
// 每次都会得到这个结果
1
2
3
4
5
6
7
2
3
4
5
6
7
# 2.1 优缺点
具有可缓存性
import _ from 'lodash'
cont sin = _.memorize(x => Math.sin(x))
// 第一次会稍慢一点
sin(1)
// 第二次有了缓存就会很快
sin(1)
```s
```js
// 不纯的
let min = 10;
let checkAge = (age) => age > min;
// checkAge 不仅取决于 age 还有外部依赖的变量 min
// 纯的 函数式
let checkAge = (age) => age > 10;
// 纯的 checkAge 把数字 10 写在函数内部,扩展性比较差,柯里化优雅的函数式解决
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 三、偏应用函数(partial application)
- 传递给函数 一部分参数 来调用它,让他 返回一个函数去处理 剩下的参数,例如:
bind
- 偏函数之所以 偏 ,就在于他只能 处理那些能与至少一个 case 语句匹配的输入,而不能处理所有可能的输入
# 3.1. 函数的柯里化
柯里化主要为了改变纯函数的硬编码方式
- 柯里化是偏应用函数的一种,他把一个 多参的函数 转换成一个 嵌套一元函数的的过程
- 传递给函数一部分参数来调用它,让他返回一个函数去处理剩下的参数
let checkAge = (min) => (age) => age > min;
let checkAge18 = checkAge(18);
checkAge18(20);
1
2
3
2
3
# 3.2 反柯里化
柯里化是接受少的参数,去处理剩下的参数,而反柯里化是尽可能实现更多的参数
区别就在于多参少参
函数柯里化
// 柯里化之前
function add(x, y) {
return x + y;
}
add(1, 2);
// 柯里化之后
function add(y) {
return function(x) {
return x + y;
};
}
add(1)(2);
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 3.3 柯里化和偏应用的区别
柯里化是一种 预加载 函数的方法,通过较少的参数,得到一个已经记住了这些参数的新函数,某种意义上是对参数的缓存
- 柯里化的参数列表是从左到右的
- 如果不能满足正常的从左到右就得使用偏应用函数了,需要额外的封装
# 四、函数组合
为了给函数解耦
纯函数以及柯里化写出来的代码h(g(s(x)))
,不容易维护,为了解决这种函数嵌套的问题,就需要用到 函数组合
函数组合的数据流是 从右到左,因为右边的函数 首先执行,将数据传递给下一个函数以此类推
// 让多个函数像拼接木一样
const compose = (f, g) => (x) => f(g(x));
const first = (arr) => arr[0];
const reverse = (arr) => arr.reverse();
const last = compose(first, reverse);
last([1, 2, 3, 4, 5]);
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
函数组合代码
compose(f, compose(g, h));
compose(compose(f, g), h);
compose(f, g, h);
1
2
3
2
3
# 五、函数组合子
之前的函数组合、函数柯里化都 分属于 函数组合子
函数组合的数据流是 从右到左,因为右边的函数 首先执行,将数据传递给下一个函数以此类推,如果不想要从右到左的,那我们可以实现一个 pipe(称作 管道、序列)来实现
组合子可以组合其他函数,作为控制逻辑单元的高阶函数,组合子通常不声明任何变量,也不包含任何业务逻辑,旨在管理函数呈现执行流程,并在链式调用中对中间结构进行操作
// 分属于 ski 组合子
# 六、Point Free
把一些对象自带的方法转化成纯函数,不要命名中间变量
函数无须提及将要操作的数据是什么样的
这个函数中使用了 str 作为中间变量,但是这个变量除了代码长一点毫无其他意义
const f = str =>str.toUpperCase().split('')
// 🏡 然后, 可以转换成下面👇这样
const toUpperCase = word => str.toUpperCase()
const split = x => (str.split(x))
const f = compose(split(' '), toUpperCase)
f("acds cds")
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 声明式与命令式代码
// 命令式
let list = [];
for (let i = 0; i < data.length; i++) {
list.push(data[i].m);
}
// 声明式
let list = data.map((i) => i.m);
1
2
3
4
5
6
7
2
3
4
5
6
7
# 类 SQL 数据:函数既数据
以函数形式对数据建模
# 惰性链、惰性求值、惰性函数
# 优点
- 代码简洁,开发快速:函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快
- 接近自然语言,易于理解
- 更方便的代码管理
- 易于"并发编程"
- 代码的热升级
- 现有的 react、redux 库是使用函数式编程
- lodash、underscore 等库原理
- node 中 koa 的原理
- 业务逻辑模块的封装
const curry = (fn) =>
(judge = (...args) =>
args.length === fn.length ? fn(...args) : (...arg) => judge(...args, arg));
const add = (a, b, c) => a + b + c;
const curryAdd = curry(add);
curryAdd(1)(2)(3);
1
2
3
4
5
6
7
2
3
4
5
6
7