JS函数式编程的三个特征#
- 拥抱纯函数, 隔离副作用
- 函数是一等公民
- 避免对状态的改变
命令式: 由具体步骤组成的命令序列, 就是一段命令式程序 声明式: 不关心具体的过程只关心输入和输出 (函数式编程是声明式的一种)
纯函数和副作用#
==question:==
- 纯函数, 副作用的内涵
- 纯函数/非纯函数的辨析
- 从数据流的角度理解纯和不纯的本质
- 纯函数解决了什么问题
什么是纯函数#
满足两个条件
- 对于同样的输入能够得到同样的输出
- 在执行过程中没有语义上可观察的副作用
什么是副作用#
除了可能的返回值之外, 对==主调用函数==产生的附加的影响
除了计算不该搞别的
什么是函数是一等公民?#
头等函数 == 函数是一等公民
头等函数的核心特征是可以被当做变量一样使用
- 可以被当做参数给另一个函数使用
- 可以作为函数的返回值
- 可以被赋值给一个变量
一等公民的本质#
JS函数是可以执行的对象
使得 --> 以函数为基本单位构建应用程序称为可能
函数组合思想(compose/pipe)#
将待组合的函数放入一个数组当中然后调用这个函数数组, 就能够创建一个多个函数组成的工作流
如何实现一个经典的Pipe函数?
jsfunction pipe(funcs) {
// 通过reduce实现流式调用, 先定义reduce所需的callback函数
function callback(accumlator, currentValue) {
// 这里accumlator就是需要进入迭代的值, currentValue就是需要链式调用的当前函数
return currentValue(initialValue);
}
// 这里的param相当于就是initialValue
return function (param) {
funcs.reduce(callback, param)
}
}
==面相对象的核心是继承, 函数式编程的核心在于组合==
多元函数解决方案: 从==编码工具==视角看偏函数&柯里化#
在JavaScript中,使用
...params(也称为剩余参数语法)作为函数的形参,允许函数接收任意数量的参数作为一个数组。这种语法在函数定义时使用,使得函数能够以更灵活的方式处理不确定数量的参数。
参数个数的对齐是组合链能够运转的前提
像是之前定义的compose函数: 链上的函数都只能够接受一个参数, 如果传入的函数需要一个以上的参数, 会导致参数不齐
柯里化#
将1个n元函数改造成n个相互嵌套的一元函数的过程
fn(a, b, c) ====> fn(a)(b)(c)
偏函数#
部分应用 通过固定一部分的参数, 生成一个参数量更少的函数的过程
==通过高阶函数封装偏函数能够避免大量重复代码,省去读函数,理解函数的过程,只需要关注函数的入参==
柯里化和偏函数#
柯里化: n元函数变成n个一元函数 偏函数: n元函数编程一个m元函数(m<n)
如何打造一个通用的curry函数#
通用柯里化函数的实现(重难点)(再看一下背一下)#
curry函数内部结合入参的情况自动判断需要套娃的层数(不管传入多少个参数, 都能够分析出参数的数量)
jsfunction carry(func, arity = func.length) {
function generateCurried(prevArgs) {
return function curried(nextArgs) {
// 统计已记忆和未记忆的参数数量
const args = [...prevArgs, nextArg]
// 如果已经大于回归函数元数, 则认为已经记忆了所有的参数
if(args.length >= arity) {
return func(...args);
} else {
return generateCurried(args);
}
}
}
// 初始为一个空数组, 表示还没有记忆任何参数
return generatedCurried([]);
}
范畴论启发下的函数设计模式(范畴论相关Intro)#
范畴论在JS当中的应用, 本质上是为了解决函数组合的问题
范畴论相关概念#
- 对象--数据
- 态射--函数
函数能够进行复合运算
g(x).f(x)
需要注意的是
- 函数运行的顺序
- 函数满足结合律
函子: 能够将一个范畴映射到另一个范畴的东西
关于盒子模式#
盒子有哪些规律:
- 盒子是一个存放数据的容器
- 数据总是以盒子入参的形式传入
- 盒子内部能够定义一系列操作数据的函数
盒子当中的Map函数需要有能够创建并且返回新盒子的能力: map函数的作用就是实现数据的映射

image.png
Functor: 盒子模式构造函数组合链#
Array就是一个Functor(函子)#
- 本身带有盛放的数据
- map()方法
- 返回一个新的box
- 做了数据的映射
Maybe Functor: 识别空数据#
如果上一个盒子产生了一个空数据, 还要想下一个进行传递, 那么下一个可能无法正常进行数据的操作, 可能会导致JS报错, 后续程序无法执行
一个MaybeFunctor的实现
jsconst isEmpty = x => x === undefined || x ===null;
const Maybe = x => {
map: f => isEmpty(x) ? Maybe(null) : Maybe(f(x)),
valueOf: () => x,
inspect: () => `Maybe {${x}}`
}
通过使用isEmpty函数能够实现空对象的判断, 如果像是传入的f是toString方法在null身上进行调用, 会报错, 但是通过MaybeFunctor的实现能够直接断掉不会实际运行f()函数
Functor需要遵守的规则#
- 恒等性
- map方法创建出来的盒子应该和原来的盒子等价
- 可组合性
- 要求Functor能够将嵌套的函数拆解为平行的链式调用
Monad: 嵌套盒子问题解法#
Monad就是"单子": 是一种特殊的函子Functor
嵌套盒子问题#
两种情况
- 线性计算场景: Functor作为另一个Functor的==计算中间态==出现
- 非线性计算场景: ==两个Functor共同作为计算入参出现==