# 函数式编程思想
为什么学习函数式编程
1、现有的redux react都有用到这些
2、node框架库用到了这些
3、能够很好的对业务进行封装
4、loadsh库的使用理解
# 一、纯函数
纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任 何可观察的副作用。
优势:不变性,使得结果可缓存
偏应用函数,与柯里化类似
# 二、柯里化
定义:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
基于偏应用函数产生的, 参数列表是从左向右的,先缓存进的是左边的参数
固定部分参数,返回一个接受剩余参数的函数,目的是为了缩小适用范围
var add = function(x) {
return function(y) {
return x + y;
};
};
var increment = add(1);
var addTen = add(10);
increment(2); // 3
addTen(2); // 12
2
3
4
5
6
7
8
9
10
反柯里化:目的是创建适用范围更大的函数
Function.prototype.uncurring=function(){
var self = this
return function(){
var obj = Array.prototype.shift.call(arguments);
return self.apply(obj,arguments)
}
}
var push = Array.prototype.push.unCurrying()
var obj = {}
push(obj,'first','two')
2
3
4
5
6
7
8
9
10
11
12
# 三、组合
compose 避免函数嵌套问题,洋葱式变成
var compose = function(f,g) {
return function(x) {
return f(g(x));
};
};
2
3
4
5
# 四、高阶函数
传入一个函数,对函数做一个封装,然后返回一个封装后的函数
dubug
var trace = curry(function(tag, x){
console.log(tag, x);
return x;
});
var dasherize = compose(join('-'),toLower,trace("after split"),split(' '),replace(/\s{2,}/ig,' '));
// after split [ 'The', 'world', 'is', 'a', 'vampire' ]
2
3
4
5
6
7
# Hindley-Milner 类型签名
代码注释,更好的解释函数的行为
//capitalize::String->String
var capitalize=function(s){
return toUpperCase(head(s))+toLowerCase(tail(s));
}
//id::a->a
var id=function(x){return x;}
//map::(a->b)->[a]->[b]
var map =curry(function(f,xs){
return xs.map(f);
});
2
3
4
5
6
7
8
9
10
11
12
13
惰性链、惰性求值、惰性函数
# 尾递归优化
递归存在了堆栈溢出风险,所以在适当时候需进行优化
尾递归:函数最后一步调用自身
尾调用:函数最后一步调用其他函数
优化:调用下一步函数时,只更新当前调用栈,不创建新的调用栈
浏览器不实现优化的原因:1、这个优化属于隐式的行为,程序员意识不到;2、堆栈信息丢失了,难以调试
Trampoline蹦床函数:对尾递归函数的处理的一种技巧,需要对递归的函数进行改造,示例如下
const sum0 = (n, prevSum = 0) => {
if (n <= 1) return n + prevSum;
return () => sum0(n-1, n + prevSum)
}
const trampoline = f => (...args) => {
let result = f(...args);
while (typeof result === 'function') {
result = result();
}
return result;
}
const sum = trampoline(sum0);
2
3
4
5
6
7
8
9
10
11
12
闭包:栈上的调用帧被释放,但是堆上的作用域并不被释放,仍可被其他函数访问,这样就形成了闭包
# 范畴与容器
**范畴:**彼此存在某种关系(函数变化)的概念、事物、对象(参数)等构成一个范畴;维基百科定义:使用箭头连接的物体
总结成模型就是:简单的理解就是"集合 + 函数"。
所有成员的集合
变形关系
**容器:**把范畴比作一个容器,那么里面包含两个东西:1、值;2、值的变形关系(函数)
代码上简单定义如下
class Category {
constructor(val) {
this.val = val;
}
addOne(x) {
return x + 1;
}
}
2
3
4
5
6
7
8
范畴论与函数式编程关系:范畴论使⽤函数,表达范畴之间的关系。发展形成一套函数运算方式,起初只应用于数学,后来在计算机上实现了,就形成了函数式编程;
本质上,函数式编程只是范畴论的运算⽅法,跟数理逻辑、微积分、⾏列式是同⼀类东⻄,都是数学⽅ 法,只是碰巧它能⽤来写程序。
函子 Functor: 遵守一些特定规则的容器类型
它的变形关系可以 依次作⽤于每⼀个值,将当前容器变形成另⼀个容器。
class Functor {
constructor(val) {
this.val = val;
}
map(f) {
return new Functor(f(this.val));
}
}
2
3
4
5
6
7
8
of ⽅法
函数式编程⼀般约定,函⼦有⼀个 of ⽅法,⽤来⽣成新的容器。
Maybe函子:为了解决容器内部值为空的问题
class Maybe extends Functor {
map(f) {
return this.val ? Maybe.of(f(this.val)) : Maybe.of(null);
}
}
2
3
4
5
Either 函⼦:函数式编程中的ifelse判断
class Either extends Functor {
constructor(left, right) {
this.left = left;
this.right = right;
}
map(f) {
return this.right ?
Either.of(this.left, f(this.right)) :
Either.of(f(this.left), this.right);
}
}
Either.of = function (left, right) {
return new Either(left, right);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
AP函子:函子的值为一个函数,同时有ap方法可以将函数应用于其他函子的值
ap 是 applicative(应⽤)的缩写。凡是部署了 ap ⽅法的函⼦,就是 ap 函⼦。
class Ap extends Functor {
ap(F) {
return Ap.of(this.val(F.val));
}
}
2
3
4
5
ap 函⼦的意义在于,对于那些多参数的函数,就可以从多个容器之中取值,实现函⼦的链式操作。
function add(x) { return function (y) { return x + y; }; } Ap.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));1
2
3
4
5
6
7
IO函子:
- IO就是输入输出,IO 函子中的 _value 是一个函数,这里是把函数作为值来处理
- IO 函子可以把不纯的动作存储(IO操作等)到 _value 中,将这个动作保存成一个IO函子,由调用者来决定什么时候来执行这个动作,这样就达到了延迟执行这个不纯的操作(惰性执行)
- 把不纯的操作交给调用者来处理
//因为IO函数需要用到组合函数,所以需要提前安装Lodash
const fp = require('lodash/fp')
class IO {
// of方法快速创建IO,要一个值返回一个函数,将来需要值的时候再调用函数
static of(value) {
return new IO(() => value)
}
// 传入的是一个函数
constructor (fn) {
this._value = fn
}
map(fn) {
// 这里用的是new一个新的构造函数,是为了把当前_value的函数和map传入的fn进行组合成新的函数
return new IO(fp.flowRight(fn, this._value))
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Monad 函⼦
class Monad extends Functor {
join() {
return this.val;
}
flatMap(f) {
return this.map(f).join();
}
}
2
3
4
5
6
7
8
9
函子的值也可以是函子,这样就存在了多层嵌套的情况,避免多层嵌套而带来的麻烦,就有了Monad,来解除嵌套
# redux中函数式编程思想的应用
store-> 容器
state -> value
action -> f
currentReducer -> map
middleware -> IO函子(异步)
redux应用了高阶函数、curry 、compose
经典函数式编程实现
函数式编程是相对于命令式编程而言的。命令式编程依赖数据的变化来管理状态变化,而函数式编程为克服数据变化带来的状态管理的复杂性,限制数据为不可变的,其选择使用流式操作来进行状态管理。而流式操作以函数为基本的操作单元,通过对函数的抽象和组合来完成整个任务。
← SOLID设计原则 面向切面编程(AOP) →