函数式编程的前世今生

函数式编程,一个新颖又难懂的模式,虽然我们现在普遍使用,闲来查查资料,学习学习原理

范畴论

1、范畴论category Theory

  1. 函数式编程是范畴论的数学分支,是一门复杂的数学,范畴轮认为世界所有的概念体系都能抽象成一个个范畴
  2. 范畴认为所有的东西彼此之间存在某种关系、事物、对象,任何事物能找出关系,就能定位成一种范畴
  3. 单线箭头表示范畴成员之间的关系,叫做“态射”。同一个范畴的所有成员是不同态的变形,通过态射转变

2、函数式编程

  1. 编程里面所有成员是一个集合,变形关系是函数
  2. 函数式编程的基础模型最早是用来计算λ(lambda x => x*2)演算,并非设计在计算机上,在后期被引用于研究函数定义、函数应用和递归的形式系统
  3. 函数式编程不是用函数编程,也不是传统的面向过程编程,主要的思想是拆分复杂的函数为简单的简单的函数。运算过程是一系列嵌套函数的调用
  4. javascript是披着C外衣的lisp
  5. 函数式编程是随着react的高阶函数的发展逐步被广大开发者使用,进而形成一个开发模式,vue3也采用了这种模式

3、javascript函数式编程的思想

  1. 函数跟别的数据类型一样,可以赋值给别的变量、作为参数传递、作为别的函数的返回值
  2. 函数在编程过程中作为参数,不能被修改,作为变量只能赋值一次,没有任何“副作用”
  3. map&&redux是最常用的函数式编程方法
  4. 没有语句,没有while,用递归替代,if-else可用三元运算符替换
  5. 引用透明(函数运行只靠参数)

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

概念:

  1. 容器包含值跟值的映射关系
  2. 映射关系就是函数
  3. 函数的运算方法 => 函数式编程
  4. 数学逻辑、微积分、行列式等等计算方法,纯函数,只为了求值,不能有业务参与,才能满足函数运算法则

一个容器通过函数运算映射编程另外一个容器,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);

你可能感兴趣的:(javascript,node.js,es6)