函数式编程
为什么要学,有什么好处?
- 函数式编程随着react的流行越来越受关注
- vue3 开始拥抱函数式编程
- 可以抛弃this
- 打包可以更好的利用tree shaking
- 方便测试,方便并行处理
什么是函数式编程
编程范式( 函数式编程 , 面向对象编程 , 面向过程编程 )之一
函数式编程用来描述数据之间的映射(不是程序中的函数(方法), 而是数学中的函数和映射关系)
函数是一等公民
- 可以存储在变量中
- 可以作为参数
- 可以作为返回值
高阶函数
- 可以帮助我们抽离通用的问题
- 屏蔽实现功能的细节,我们只需要关注目标
闭包
- 有权 访问 另一个函数作用域中 变量 的 函数
- 简单理解就是: 如果这个函数作用域可以访问另一个函数内部的局部变量,那就产生了闭包,另外那个作用域所在的函数称之为 闭包函数
闭包的本质: 函数在执行的时候会放在一个执行栈上,当函数执行完会被移除,但是因为作用域上的变量被引用所以不会被移除,因此内部函数依然可以访问外部外部函数的成员
纯函数
- 相同的输入始终会得到相同的输出, 而且没有任何观察的副作用
- loadsh是一个纯函数的功能库
- 函数式编程不会保留计算中间的结果,所以变量是不可变的(无状态的)
- 我们可以把一个函数的执行结果交给另一个函数去处理
// 纯函数和不纯函数// slice/splice// splice会改变原数组
纯函数的好处
可缓存
- 因为纯函数对相同的输入总会有相同的输出,所以可以把结果缓存起来
- 可测试
可并行处理
- 多线程环境下并行操作共享的内存数据很可能会出现意外
- 纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数
副作用
副作用可以使一个纯函数变得不纯.如果函数依赖外部的状态,就无法保证输出相同,就会带来副作用
来源
- 配置文件
- 获取用户的输入
- 数据库
- ……
所有的外部交互都有可能带来副作用,会使得方法通用型下降,延展性下降,重用性下降.但副作用不可能完全禁止,尽可能控制在可控范围内发生
柯里化
概念
- 当一个函数有多个参数的时候先传递一部分参数调用他(这一部分参数是不变的)
- 然后返回一个新函数接收剩余参数
总结
- 柯里化可以让我们可以得到一个已经记住部分参数的新函数
- 可以实现部分参数的缓存
- 可以让函数的颗粒度更小,让函数变得更灵活
- 多个一元函数可以组合成多元函数,使功能更强大
函数组合
为什么
- 柯里化和纯函数很容易写出洋葱代码 g(h(f(x)))
- 函数组合可以让我们把细粒度的函数重新组合成一个新函数
概念
函数组合: 如果一个函数要经过多个函数处理才能得到最终值,那么我们可以把中间过程函数合并成一个函数
- 函数就是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成最终的结果
- 函数组合默认是从右往左执行的
loadsh中的组合函数 flow() / flowRight()
flow()是从左到右的, flowRight()是从右到左的.
函数的组合要满足结合律
com(com(f, g), h) ==com(f, com(g, h))// => true
调试
const_=require('lodash')const trace=_.curry((tag, v) => {console.log(tag, v)return v})const split=_.curry((sep, str) =>_.split(str, sep));const join=_.curry((sep, array) =>_.join(array, sep));const map=_.curry((fn, array) =>_.map(array, fn));const f=_.flowRight(join('-'),trace('map 之后'),map(_.toLower),trace('split 之后'),split(' '))console.log(f('NEVER SAY DIE'))// 利用柯里化简化参数,接收参数并打印返回,达到调试的目的
Point Free
我们把数据处理的过程定义成与数据无关的合成运算.不需要用到代表数据的那个参数,只要把运算步骤合成到一起,在使用之前我们需要定义一些简单的辅助函数.
- 不需要指明处理的数据
- 只需要合成运算过程
- 需要定义一些辅助的基本运算函数
函子
学习目的
- 为了在函数式编程中把副作用控制在可控的范围内,异常处理,异常操作等
是什么
- 容器: 包含值与值的变形关系(函数)
- 函子: 是一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)
函子总结
函数式编程不直接操作值,而是由函数来完成
函子就是一个实现map的对象
可以把函子想象成一个盒子,里面封装了一个值
想要处理这个值,我们就要给盒子的map方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理
最终map方法返回一个包含新值的盒子(函子)
MayBe 函子
- 可以处理外部的空值情况
Either 函子
- 类似if else 的处理.处理异常
IO 函子
- IO函子中的_value是一个函数,把函数当成值来处理
- IO函子可以把不纯的动作存储到_value中, 延迟执行这个不纯的操作(惰性),
- 把不纯的操作交给调用者来处理
Task 异步执行
Pointed 函子
Monad(单子)
