函数式编程
- 函数式编程(FP)与面向对象编程(OOP):面向对象编程是对事物的抽象,而函数式编程是对运算过程的抽象。
- 抽象的意义:抽象可以帮我们屏蔽细节,我们只需要知道我们的目标和解决这类问题的函数,我们不需要关心实现的细节。
- 高阶函数(Higher-order function):把函数作为参数,作为返回值的函数。
- 常用的高阶函数:forEach、map、filter、every、some、find/findIndex、reduce、sort。
- 闭包:常见情况就是高阶函数返回一个函数,而在返回的函数中引用了高阶函数周围词法环境的变量从而使得他们捆绑在一起进而使高阶函数内部成员的作用范围及时间延长。即:函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包。
- 纯函数:相同的输入永远会得到相同的输出(不会改变传入参数)。没有副作用(不依赖外部状态,如:配置文件,数据库,用户输入等)。
- 纯函数的优点:可缓存(因相同输入始终有相同输出,可用来优化递归)、可测试、可并行(纯函数不需要访问共享的内存数据)。ES6中有Web Worker,可以开启一个新线程。
- 函数的柯里化:当函数有多个参数时,可以调用此函数但只传递部分的参数(这部分参数以后永远不变),然后让这个函数返回一个新的函数。新的函数传递剩余的参数,并且返回相应的结果。(使用了闭包对函数部分参数进行缓存)能让函数的粒度更小从而进行相应函数组合。
- Lodash:一个纯函数的功能库(安装:npm i lodash)。官方文档:https://www.lodashjs.com/
- Lodash中的柯里化:curry(func)
- 功能:创建一个函数,该函数接收一个或多个 func的参数,如果 func 所需要的参数都被提供则执行 func 并返回执行的结果。否则继续返回该函数并等待接收剩余的参数。
- 参数:需要柯里化的函数
- 返回值:柯里化后的函数
- 函数组合(compose):若一个函数要经过多个函数处理才能得到最终值,则可通过函数组合把细粒度的中间过程的函数重新组合生成一个新的函数,避免写出“洋葱代码”。注:函数组合默认是从右到左执行的。
- Lodash中的组合函数:flow() 、flowRight()。其中:flow() 是从左到右运行的,而flowRight() 是从右到左运行的,使用的更多一些。
- 函数组合之结合律(associativity):compose(compose(f, g), h) == compose(f, compose(g, h)) 。
- 函数组合之调试:可在各函数组合之间调用输出函数从而输出部分函数组合后的结果。
- Lodash中的FP模块提供了实用的对函数式编程友好的方法(函数、规则置先,数据置后)。
- Point Free风格:把数据处理的过程定义成与数据无关的合成运算,不需要用到代表数据的那个参数,只要把简单的运算步骤合成到一起的编程风格。(需要定义一些辅助的基本运算函数)
- 函子(Functor):函子是一个特殊的容器(容器:包含值以及值的变形关系),通过一个普通的对象来实现,该对象具有 map 方法,map方法可以运行一个函数对值进行处理(变形关系)。
- 函子的作用:为了尽可能的将副作用控制在可控的范围内,可以通过函子去处理副作用,处理异常,异步操作等。
- Maybe函子:可以对外部的空值情况做处理(控制副作用在允许的范围)。
- Either函子:Either(两者中的任何一个),类似于 if…else…的处理(用try{返回正确函子}catch(e){返回错误函子并有错误提示})。异常会让函数变的不纯,Either 函子可以用来做异常处理。当出现问题时,Either函子可以给出提示的有效信息。(因为是二选一,所以要定义left和right两个函子)
- IO函子:IO就是输入输出,IO 函子中的值是一个函数,即把函数作为值来处理。IO函子可以把不纯的动作存储到值中,延迟执行这个不纯的操作(惰性执行),包装当前的操作从而把不纯的操作交给调用者来处理。(用组合函数来返回对应函数)
- Task函子:可以处理异步任务,实现复杂,可用folktale库中提供的task函子。在需要完成某功能的函数中返回task函子(在版本2中task为函数,返回task函子对象,1中为类,以版本2为例),参数为一个函数,参数函数有resolve和reject方法,功能与Promise类似。调用对应函数时需要用run方法(在run之前可先调用map方法,在map方法中会处理函数的返回结果),再用listen方法传入一个对象,有onResolved和onRejected属性,值为函数,参数为函子的参数函数中resolve和reject方法传入的值。
- Pointed函子:作用是把值放到一个新的函子里面返回,返回的函子就是一个上下文,之后在上下文中处理数据(用一个静态的of方法实现,传入一个值,返回用传入值构造的函子对象)。
- Monad函子(单子):Monad函子是可以“变扁”的 Pointed 函子,用来解决IO函子嵌套问题。一个函子如果具有 join(返回IO函子值的调用,在IO函子中其保存的值为函数)和静态IO方法(静态of方法返回IO函子)并遵守一些定律就是一个 Monad。
- 何时用Monad:当一个函数返回一个函子时。且假如我们想进行相关处理时,若想要返回一个函数,而这个函数返回一个值,可以调用map方法;若我们想要去合并一个函数,但是这个函数返回一个函子,这个时候要用flatMap方法。
部分函子实现:
// Maybe函子
class Maybe {
static of(value){
return new Maybe(value)
}
constructor(value){
this._value = value
}
map(fn){
return this.isNull() ? Maybe.of(null) : (this.isUndefined() ? Maybe.of(undefined) : Maybe.of(fn(this._value)))
}
isNull(){
return this._value === null
}
isUndefined(){
return this._value === undefined
}
}
// Pointed函子
class Container{
static of(value){
return new Container(value)
}
constructor(value){
this._value = value
}
map(fn){
return Container.of(fn(this._value))
}
}
// Either函子之Left
class Left{
static of(value){
return new Left(value)
}
constructor(value){
this._value = value
}
map(fn){
return Left
}
}
// Either函子之Right
class Right{
static of(value){
return new Right(value)
}
constructor(value){
this._value = value
}
map(fn){
return Right.of(fn(this._value))
}
}
// IO函子,需要先定义函数组合
function compose(...args){
return function(value){
// 从右往左,所以需要先reverse
return args.reverse().reduce((acc, fn) => {
return fn(acc)
},value)
}
}
class IO{
static of(value){
return new IO(() => value)
}
constructor(fn){
this._value = fn;
}
map(fn){
// 这里用new而非of的目的是传入fn与原_value进行组合返回新函数,只因_value是函数(虽然功能只是返回值)所以需要这么做
return new IO(compose(fn,this._value))
}
}
// TODO: Task函子
// Monad函子,具有静态的IO方法(静态of方法的行为与IO的静态of类似)和join方法的函子
class Monad{
static of(value){
return new Monad(() => value)
}
constructor(fn){
this._value = fn
}
map(fn){
return new Monad(compose(fn,this._value))
}
join(){
return this._value()
}
// flatMap:同时调用map和join方法
flatMap(fn){
return this.map(fn).join()
}
}