函数式编程,一个新颖又难懂的模式,虽然我们现在普遍使用,闲来查查资料,学习学习原理
范畴论
1、范畴论category Theory
- 函数式编程是范畴论的数学分支,是一门复杂的数学,范畴轮认为世界所有的概念体系都能抽象成一个个范畴
- 范畴认为所有的东西彼此之间存在某种关系、事物、对象,任何事物能找出关系,就能定位成一种范畴
- 单线箭头表示范畴成员之间的关系,叫做“态射”。同一个范畴的所有成员是不同态的变形,通过态射转变
2、函数式编程
- 编程里面所有成员是一个集合,变形关系是函数
- 函数式编程的基础模型最早是用来计算λ(lambda x => x*2)演算,并非设计在计算机上,在后期被引用于研究函数定义、函数应用和递归的形式系统
- 函数式编程不是用函数编程,也不是传统的面向过程编程,主要的思想是拆分复杂的函数为简单的简单的函数。运算过程是一系列嵌套函数的调用
- javascript是披着C外衣的lisp
- 函数式编程是随着react的高阶函数的发展逐步被广大开发者使用,进而形成一个开发模式,vue3也采用了这种模式
3、javascript函数式编程的思想
- 函数跟别的数据类型一样,可以赋值给别的变量、作为参数传递、作为别的函数的返回值
- 函数在编程过程中作为参数,不能被修改,作为变量只能赋值一次,没有任何“副作用”
- map&&redux是最常用的函数式编程方法
- 没有语句,没有while,用递归替代,if-else可用三元运算符替换
- 引用透明(函数运行只靠参数)
4、纯函数
函数不依靠外部变量产生运行结果,相同的输入得到的输出永远相同,不会改变原数据的值,slice和splice区别,前者纯后者不纯
特性:可缓存,相同的运算初次运行慢,后序运行很快,例如lodash的memorize函数可以记录一个函数的计算过程
5、函数柯里化:预加载函数,缓存参数,高效运行
传递一部分参数来调用,返回一个函数去处理剩下的参数,如果计算过程链很长,可以多级化处理
function add(x, y) {
this.val = x + y;
}
let str = add.bind(null, "str"); // 防止this乱指向,bind一个null
let s = new str("ing");
s.val // string
// 使用lodash的curry函数
let match = curry((reg, str) => str.match(reg));
let filter = curry((f, arr) => arr.filter(f));
let hasSpace = match(/\s+/g);
let arr = ["abcd", "ac bd"]
filter(hasSpace)(arr);
6、函数组合:
洋葱模型h(g(f(x)))柯里化改造,拼积木
const compose = (g, f) => x => g(f(x));
let first = arr => arr[0];
let reverse = arr => arr.reverse();
let last = compose(first, reverse);
last([1,2,3,4]);
7、Point Free
把对象自带的方法转换成纯函数,不要转瞬即逝的中间变量
const f = str => str.toUpperCase().split("");
// 改造一下·········:
let toUpperCase = word => word.toUpperCase();
let split = x => (str.split(x));
let f = compose(split(''), toUpperCase);
f("ab cd");
8、声明式与命令式代码
声明式:函数式编程的使用
let books = bookShop.map(s => s.book);
命令式:
let books = [];
for(let i = 0,;i
9、惰性求值
例如封装ajax,每次在做请求的时候需要判断是什么内核,再执行,进入一次执行,直接把函数缓存,只执行一次,减少并发接口处理的判断流
function ajax(){
let xhr = null;
if(window.XMLHttpRequest) {
xhr = new XMLHttpRequest;
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP")
}
ajax = xhr
return xhr;
}
继承,封装,多态=>高密度,低耦合
10、高阶函数
函数作为参数使用
11、尾调优化
函数内最后一步操作是函数调用,调用的返回值直接给函数;如果函数是自身,就是尾递归函数。
递归需要保存大量的调用记录,数量大会栈溢出错误,尾递归优化,将递归变成循环,只需要保存一个调用记录,不会发生栈溢出的错误
注:ES6强制使用尾递归优化
function f(n) {
if(n ==1) {
return 1;
}
return n*f(n-1);
} // 不是尾递归,有参数参与
function f(n, t) {
if(n ==1) {
return t;
}
return f(n-1, n*t);
} // 尾递归,只保留一次调用记录
尾递归:函数最后一步调用自身,不是最后一行,调用别的函数叫尾调用
尾递归目前问题:
现代浏览器并未完全支持,只更新当前栈内存信息
执行引擎消除递归,开发人员不知道
堆栈执行信息丢失,难调试
12、闭包
函数域栈调用帧被释放,函数内部的变量被堆内存保存下来,然后返回回调函数被外部使用,私有变量可以被访问到
function p(x) {
function g(y) {
return Math.pow(y, x);
}
return g;
}
var square = f(2);
square(3); // 9
容器、Functor
概念:
- 容器包含值跟值的映射关系
- 映射关系就是函数
- 函数的运算方法 => 函数式编程
- 数学逻辑、微积分、行列式等等计算方法,纯函数,只为了求值,不能有业务参与,才能满足函数运算法则
一个容器通过函数运算映射编程另外一个容器,Functor就是这两个容器映射关系
Functor作为函数的特殊使用,把数据值装入容器,预留map出口,让别的函数调用通过映射关系得到的新值
// ES5
var Functor = function (x) {
this.__value = x;
}
Functor.of = x => new Functor(x);
Functor.prototype.map = function (f) {
return Functor.of(f(this.__value));
}
Functor.of(3).map(x => x+1).map(x => 'elel' + x);
// ES6
class Functor {
constructor(val) {
this.val = val
}
map(f) {
return new Functor(f(this.val));
}
}
1、Maybe
函数式编程没有try、catch和if、else,所以处理外部传入的空值和判断需要一个Functor操作(三元判断)
Functor.of(null).map(function(s) {
return s.toUpperCase();
});
// ES6
class Maybe extends Functor {
isNothing() {
return (this.val === null || this.val === undefined);
}
map(f) {
return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.val));
}
}
Maybe.of(null).map(function(s){
return s.toUpperCase();
});
// ES5
var Maybe = function (s) {
this._value = s;
}
Maybe.of = function (s) {
return new Maybe(s);
}
Maybe.prototype.map = function(f){
return this.isNothing ? Maybe.of(null) : Maybe.of(f(this._value));
}
Maybe.prototype.isNothing = function () {
return (this._value === null || this._value === undefined);
}
2、错误处理、Either、AP
容器需要处理一些特殊情况,promise可以调用catch处理错误,either表示或的逻辑操作
either:函数式里面替换if··else,代替try···catch,分为左右值,正常是右值生效,若右值不存在,则左值作为默认值生效
class Either extends Functor {
constructor(left, right) {
this.left = left;
this.right = right;
}
map(f) {
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);
}
class Left extends Functor {
constructor(x) {
this._value = x;
}
map(f) {
return this;
}
}
class Right extends Functor {
constructor(x) {
this._value = x;
}
map(f) {
return Right.of(f(this._value));
}
}
3、IO
惰性求值
包裹非纯函数的操作或者数据(http、DOM),留给外部运行环境处理,它的__value是一个函数
Monad:一种设计模式,例如promise
函数的输入输出是一个数据类型,是一个数据转换成另一个数据的装箱过程,把两个容器的值拿出来,操作完再生成新的容器丢出去给别的用。过程是数据类型之间的运算,黑盒过程
class Functor {
constructor(val) {
this.__value = val
}
map(f) {
return new Functor(f(this.__value));
}
}
class Monad extends Functor{
join() {
return this.__value;
}
flatMap(f) {
return this.map(f).join();
}
}
class IO extends Monad{
map(f) {
return IO.of(compose(f, this.__value));
}
}
let readFile = function(filename) {
return new IO(function() {
return fs.readFileSync(filename, 'utf-8');
});
}
readFile('./user.txt').flatMap(tail).flatMap(print);