什么是函数式编程?

函数式编程(Functional Programming,FP)是纯函数的一种编程范式,是一种编写方式,和编写风格。

通俗的讲:函数式编程的概念来自数学中的函数的映射关系。一个函数的返回值,仅仅依赖于参数的值,而不会因为其他外部的状态不同而不同

例如:一个求幂的函数pow(base, exponent),它的计算结果仅仅依赖于base(基数),和exponent(指数)的不同而不同。这个函数无论我们调用多少次,只要参数一致,那么返回值绝对一致。

函数式编程跟面向对象编程和面向过程编程是并列的,面向对象编程是将现实中的事物抽象成类和对象,用程序描述事物之间的联系;面向过程编程是按照步骤一步步实现的过程。

把运算过程抽象成一个函数,任何地方都可去重用这些函数,屏蔽实现的细节,只要管目标的逻辑。

抽象出来的函数是细腻的函数,可以将其组合成功能更加强大的函数

为什么要学习函数式编程?

1、在React和vue2.0~3.0的框架中大量的使用到了函数式编程范式的高阶函数,说明是主流的趋势。

2、可以提高代码的复用,可以摒弃this。

3、在打包时也可以更好的利用tree shaking 过滤无用的代码。

函数是一等公民

1、函数是对象可以作为值存储在变量 【就是函数表达式】;

2、函数可以作为参数传递给另一个函数【使用高阶函数实现es6中的forEach、map、filter方法】;

3、函数可以作为另外一个函数的返回值【闭包、函数柯里化等】;

函数是一等公民的三个特点是高阶函数和函数柯里化的基础。

高阶函数

高阶函数是函数式编程的一个特性

应用:实现forEach、filter、map、from等

把运算过程抽象成一个函数,不需要关注循环的具体实现

函数作为值赋值遍历

var fun = function(){
	console.log('hello world')
}

函数做参数

应用:实现ES6数组常用函数forEach、filter等

//模拟forEach
function forEach(arr, fun){
    if(!(arr instanceof Array)) return false;
    for(var i = 0; i < arr.length; i++) {
        fun(arr[i]);
    }
}
//将运算抽象出来
forEach(array, (item) => {
    console.log(item)
})
//模拟filter过滤数组
function filter(arr, fun) {
    if(Object.prototype.toString.call(arr) != "[object Array]") return false;
    const array = [];
    for(var i = 0; i < arr.length; i++) {
        if(fun(arr[i])) array.push(arr[i]);
    }
    return array;
}
//将运算抽象出来
const nextArr = filter(array, (item) => {
    return item % 2 == 0;
})
console.log(nextArr)
//模拟 from 将对象转数组
function from(obj, fun) {
    if(!(obj instanceof Object)) return false;
    //Obj.getOwnPropertyNames 返回对象第一层的属性数组
    const key = Object.getOwnPropertyNames(obj), value = [];
    for(var i = 0; i < key.length; i++) {
        value.push(fun(obj[key[i]]))
    }
    return value;
}
var fromArr = from({a:1,b:2,c:3}, (item) => {
    return item;
})
console.log(fromArr)
//模拟map写法
function map (arr, fun) {
    if(!(arr instanceof Array)) return false;
    let array = [];
    for(var i = 0; i < arr.length; i++) {
        arr[i] && array.push(fun(arr[i]))
    }
    return array;
}
array.length = 20;
var mapArr = map(array, function(item){
    return item += 1;
})
console.log(mapArr)
//模拟every 实现一假即假
function every(array, fun){
    if(!(array instanceof Array)) return false;
    for(var value of array){
        if(!fun(value)) return false;
    }
    return true;
}
var he = every(array, item => {
    // if(item < 10) return true;
    // return false;
    return item < 10;
})
console.log(he)
/**
 * 模拟some 实现一真即真
 */
function some(array, fun) {
    if(!(array instanceof Array)) return false;
    for(var value of array) {
        if(fun(value)) return true;
    }
    return false;
}
//1
var so = some([11,2,3, 0], item => {
    return item > 10;
})
//2 代码最少,一行代码逻辑可以参考这种
var so = some([11,2,3, 0], item => item > 10)
console.log(so)
/**
 * 模拟find/findIndex
 */
// 实现find
function find(array, fun) {
    if(!(array instanceof Array)) return false;
    for(var i = 0; i < array.length; i++) {
        if(fun(array[i])) return array[i];
    }
}
var he = find(array, item => item > 5);
console.log(he)

//findIndex
function findIndex(array, fun) {
    if(!(array instanceof Array)) return false;
    // for(var i = 0; i < array.length; i++){
    for(var index in array){
        if(fun(array[index])) return index;
    }
}
const ha = findIndex(array, item => item > 7)
console.log(ha)
/**
 * 模拟reduce
 */
function reduce(array, fun, init){
    if(!(array instanceof Array)) return false;
    let Cumulative,index;
    init || init === 0 ? (index = 0, Cumulative = init) : (index = 0, array[0])
    for(index; index < array.length; index++) {
        Cumulative = fun(Cumulative, array[index], index, array)  
    }
    return Cumulative;
}

var haha = reduce([1,2,3,4,10,1], (prev, cur) => {
    //return prev + cur;//求数组之和
    //return prev = prev > cur ? prev : cur; //求数组项最大值
    //return prev.includes(cur) ? prev : [...prev,cur];  //数组去重
    
    //prev[cur] ? prev[cur] ++ : prev[cur] = 1; //计算数组中每个元素出现的次数
    //return prev
}, 0)

函数作为返回值

应用:闭包、函数柯里化、防抖、节流等

生成只执行一次的函数
function owen(fun){
    let state = false;//标记生成函数执行的状态;
    return function(){
        if(!state){ //只有在没有执行的才能进入
            state = true;// 执行过后将状态改变
            fun.apply(this,arguments);
        }
    }
}
var create = owen(function(mas){
    console.log(mas)
})
create(1)
create(1)
create(1)
闭包
描述

什么是闭包:闭包是在一个函数能访问另外一个函数内部成员变量的函数,通俗的讲:我拿着你给我的钥匙,我可以拿着这个钥匙进入你家那东西。

正常函数的执行:当一个函数在执行的时候,会将函数放在执行栈上执行,执行完释放。

闭包的实现:当一个函数内部返回一个函数,返回的函数对外层的函数的成员有引用的时候,闭包就实现了。

原理:函数在执行的时候会将函数放到执行栈上执行,当函数执行完毕后从执行栈上移出,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依旧可以访问外部函数的成员。(在下面的浏览器调试可以观察到)

优点:延长的内部函数(retrurn的函数)的作用域范围

缺点:过多的使用闭包使得内存被占用就是内存泄漏。

闭包经典案例:
//防抖
function debounce(){
	let time = null;
	return function(){
        if(time) clearTimeout(time)
        time = setTimeout(() => {
            console.log('执行')
        },1000)
    }
}
var debou = debounce()
debou()
debou()
debou()
debou()

//结合上面的高阶函数作为参数的写法,将防抖执行抽离出来
function debounce(fun,wait){
    let time = null;
    return function(){
        if(time) clearTimeout(time);
        time = setTimeout(fun,wait)
    }
}
var debou = debounce(function rollFun(){
   //只要将的函数当做实参传入就行
   console.log('业务代码')
},1000)
debou()
debou()
debou()
通过Chromes深入闭包

如图:Call Stack 是函数调用栈,Scope 是当前作用域,作用域是Global全局window作用域,this指向也是window,调用栈里代码都是在anonymous这个匿名函数里调用的。

在这里插入图片描述

当函数在执行的时候,Call Stack函数调用栈的栈顶是makePower这个函数,当调用makePower函数的时候,Scope的作用域是Local局部作用域*(函数内部的)*,函数的内部指向是window,name和power是函数内部成员

在这里插入图片描述

let是有独立的作用域,不会挂载到window对象上,如果是定义的是var,就会挂载到window对象上,需要点开Global里找

在这里插入图片描述

当函数在外部调用的时候,会创建一个函数作用域,this指向window,注意看框起来的的地方,重点来了!!! 在Scope作用域里出现了一个 【Closure 闭包、闭合】 跟闭包相关的外部函数是makePower, Closure里面是跟闭包相关的变量,但是name没在里面,只有power,power是跟闭包相关的变量,因为堆上的作用域成员(power)因为被外部引用不能释放,但是name没有被引用被释放。

在这里插入图片描述

当函数执行完正常销毁,因为make1这个函数里面没有return一个函数,所以正常走销毁。

在这里插入图片描述

纯函数

纯函数不会依赖外部的值,相同的输入始终得到相同的输出【就是在重复调用一个函数的时候,值传的是一样,输出的结果永远是一样】,没有观察的副作用。

纯函数优点:

可以基于相同的输入始终得到相同的输出这优点,将计算的值缓存,对于计算量大的递归调用,可以加快速度,实现一个函数只执行一次,无需重复计算,使用存函数实现memoize。

纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数。

es6后有web Worker开启多线程

function getArea(r){
    return Math.PI * r * r;
}
function memoize(fun){
    let cache = {}
    return function(){
        let key = JSON.stringify(arguments);
        //return cache[key] || (cache[key] = fun.apply(fun,arguments)) // 使用apply
        return cache[key] || (cache[key] = fun(...arguments)) //使用解构
    }
}
const getMemoizeWidth = memoize(getArea);
console.log(getMemoizeWidth(4));
console.log(getMemoizeWidth(4));
console.log(getMemoizeWidth(4));
纯函数缺点:

计算的值依赖外部的变量就会照成不纯函数。

纯函数示例:
//纯函数
var arr = [1,2,3,4,5,6,7];
arr.slice(0,3);
arr.slice(0,3);
arr.slice(0,3);

function add(num1, num2){
   return num1 + num2;
}
console.log(1,2);
console.log(1,2);
console.log(1,2);

//硬编码的纯函数[硬编码就是基值是固定的],编程尽量避免,使用函数柯里化解决
function addsum(age) {
    let min = 16;
    return min + age;
}
console.log(addsum(11))
console.log(addsum(11))
console.log(addsum(11))

//柯里化
function addsum(num1){
    return function(num){
        return num1 + num;
    }
}
let add = addsum(10);
console.log(add(11))
console.log(add(11))
console.log(add(11))
不纯函数

当函数内部的值依赖函数外部的状态就无法保证输出相同,不纯函数在重复调用并且参数一样输出的结果不一样。

不纯函数来源
配置文件、数据库、获取用户的输入、全局变量
不纯函数缺点

降低通用性、不适合扩展和可重用性,以及安全隐患给程序带来不确定性,和跨站交互攻击XSS,无法完全禁止,尽量控制发生。

不纯函数示例
//依赖外部的值会出现相同输入的值,输出的值不一样
var num1 = 11;
function add(num2){
    return num1 + num2;
}
console.log(add(1))
num1++;
console.log(add(2))
高阶函数库Lodash

我的理解是一个封装了高阶函数的js的方法库,工具集,或者函数式编程的方法集合,没有参数的函数不是纯函数,比如说用高阶函数手写es6的一些方法,例如:filter、forEach等。

函数柯里化
柯里化描述

当一个函数有多个参数并调用的时候,只接收一部分参数【这些参数是不会变的】,并且返回一个函数接收剩下的值,处理并返回结果。

可以将多元函数转成单元函数【多元函数就是多个参数的函数,单元就是一个函数的参数】

function addSum(num1){ 
    return function(num2){
        return num1 + num2;
    }
}
var add = addSum(11)
console.log(add(31))
柯里化经典面试题

实现curried(1,2,3) curried(1)(2,3) curried(1,2)(3) curried(1)(2)(3)

思路:函数名.length拿到形参列表,arguments拿到实参列表,当实参的数量小于形参就需要返回一个函数接受剩余参数

/**
 * 使用柯里化将一元函数转成多元函数
 * 描述:开始的时候只传一部分参数,返回一个函数处理剩下的参数
 * 思路:函数名.length可以拿到函数的所有形参列表;函数形参通过...agrm 可以拿到所有的实参
 * 
 * 实现功能:
 * curry 方法传入一个方法 实现 curried(1,2,3)  curried(1)(2,3)   curried(1,2)(3) curried(1)(2)(3)
*/

function addNum(a, b, c){
    return a + b + c;
}
function curry(fun){
    return function curriedFun(...argum) {
        if(fun.length > argum.length){ //1、也就是传进来的实参的数量没有满足,我们需要返回一个函数接受剩下的参数
            return function(){
                /**
                 * 3、我们需要拿到上次的实参,结合这次的实参,判断是否fun形参的数量,需要自调用
                 * 
                 * 解析步骤三的写法
                 * arguments 是伪数组,需要转成数组Array.from(arguments) 
                 * argum.concat(Array.from(arguments)) , 需要将上一次的实参跟这次的实参合并,并传入fun方法中
                 * ...argum.concat(Array.from(arguments)),在前面加...是拥有解构,不解构等价实参传了一个数组,就是一个参数,需要解构成1,2,3多元的参数
                 * 结果返回
                 */
                return curriedFun(...argum.concat(Array.from(arguments)))
            }
        }else if(fun.length <= argum.length){ // 2、当实参满足实参的数量可以直接执行
            return fun(...argum);
        }
    }
}
var curried = curry(addNum);
console.log(curried(1,2,3))
console.log(curried(1)(2,3))
console.log(curried(1)(2)(3))
console.log(curried(1)(2))
柯里化总结

柯里化能生成一个新的函数,并且能记住已有固定的参数(通过闭包对函数的参数进行缓存);

能生成一些更细腻的函数,可以实现函数组合;

可以把多元函数(多个参数)转成一元函数(一个参数),组成更加强大的函数;


组合函数

组合函数描述

函数组合(compose):如果一个函数需要经过多个函数进行处理才能得到最终的结果,这个时候可以把中间的过程组合成一个函数。

函数式编程把运算过程抽象成一个函数,组合函数将抽象出来的函数合成一个新的函数,合成的过程依旧是抽象的过程,我是不需要关心数据的

组合函数概念

纯函数和柯里化很容易写出洋葱代码,就是嵌套很深的代码 x( g( h( b(a) ) ) ),一层层包裹,就像洋葱一样;

函数组合可以把一些细腻的函数组合成一个新的函数

在函数中处理数据的过程,给fn函数输入参数a,返回结果b,可以想想a数据通过一个管道得到了b数据。如果fn这个管道足够长,出现了问题很难找到出现问题的地方。

image-20210609075522341

当fun函数笔记复杂的时候,可以把函数fn拆分成多给小的函数,此时多了中间运算过程产生的m和n。

可以把fn这个管道拆分成了3个管道f1,f2,f3,数据a通过管道f3得到结果m,m在通过管道f2得到结果n,n通过管道f1 得到最终结果b。

image-20210609080241116

const fn = compose(f1, f2, f3)
b = fn(a)

Lodash中的组合函数

lodash中组合函数有 flow() 和 flowRight();

flow() 从左到又执行,flowRight() 是从右到左运行。

/**
 * locash 的使用
 * npm i --save lodash
 * 如果代码放到了git 记得在.gitignore文件上加 node_modules/ 防止依赖包被提交
 */
const _ = require('lodash')
const reverse = arr => arr.reverse();
const first = arr => arr[0];
const toUpper = s => s.toUpperCase();
const f = _.flowRight(toUpper, first, reverse)
console.log(f(["a","b","ca"]))

组合函数的结合使用

const _ = require('lodash')
const f = _.flowRight(_.toUpper, _.flowRight(_.first, _.reverse))
console.log(f(["a","b","ca"]))

组合函数的调试

使用flowRight实现
//组合函数
//输入 NEVER SAY DIE  输出---> never-say-die 
const _ = require('lodash');
const split = _.curry((sep, str) => _.split(str, sep));
const map = _.curry((arr) => _.map(arr, _.toLower));

//const join =  _.curry((sep, arr) => _.join(sep,arr));
const join =  _.curry((sep, arr) => _.join(arr,sep));//改成_.join(sep, arr); 

const log = _.curry((name, value) => {
    console.log(name,value)
    return value;
})
console.log(split(' ')('NEVER SAY DIE'))
const f = _.flowRight(log('join 的执行结果'), join('-'),log('map函数的执行结果'), map, log('split 执行结果'), split(' '))
console.log(f('NEVER SAY DIE'))

执行结果:可以通过插入函数拿到上个函数的结果找出问题.

image-20210609212448806

使用flow实现:
const _ = require('lodash');
const split = _.curry((sep, str) => _.split(str, sep));
const join =  _.curry((sep, arr) => _.join(arr, sep));
const map = _.curry((arr) => _.map(arr, _.toLower));
const f = _.flow([split(' '), map, join('-')])
console.log(f('NEVER SAY DIE'))

Pointfree

其实就是函数的组合

//输入 hello    world  ==>  hello_world
const fp = require('lodash/fp')
//fp.replace()
const f = fp.flowRight(fp.replace(/\s*/g,"_"),fp.toLower)
console.log(f("hello    world"))

Lodash中的函数式编程

使用lodash的方法的时候没有被柯里化,都是数据优先,函数滞后,每次都需要掉curry柯里化的函数进行处理。

所以需要使用lodash中的fp 函数式编程模块,fp模块都是函数优先数据滞后。

const fp = require('lodash/fp');
const f = _.flowRight(fp_join('-'), fp.map, fp.split(' '))
console.log(f('NEVER SAY DIE'))

lodash中fp文档:https://github.com/lodash/lodash/wiki/FP-Guide

实例参考官网:https://lodash.com/

函子

Functor函子

描述

为什么要学习函子

1、在函数式编程中使用函子把副作用控制在可控的范围内,异常处理和异步操作。

什么是Functor

容器:包含值和值的变化惯性(变化关系就是函数)

函子:是一个特殊的容器,通过一个普通对象实现,该对象上定义一个map方法或者其他方法,这个方法可以运行一个函数对值进行处理(也就是接受一个函数)。

最基础的函子

class Container {
    constructor(value){ //接受一个变量,提供维护,不对外开发
        this._value = value;
    }
    
    map(fun){// 纯函数,接受一个处理值的函数
        return new Container(fun(this._value)); //把处理的结果返回给新的函子来保存
    }
}
//始终不对外公布内部的值,永远不会去取出里面的值,如果想处理值,调用map方法去处理和打印
const r = new Container(5).map(x => x + 1).map(x => x * x); //可以连续的.map 进行链式编程。
console.log(r) //返回的一个新的函子,


//上面的写法偏向面向对象编程,需要更改
class Container {
    static of(value) {
        return new Container(value);
    }
    constructor(value) {
        this._value = value;
    }
    map(fun) {
        return Container.of(fun(this._value));
    }
}
const r = Container.of(5).map(x => x + 1).map(x => x * x);
console.log(r) 

Functor 函子总结

函数式编程的运算不能直接操作,而是由函子完成。

函子就是一个实现了map契约的对象。我们可以把函子想象成一个盒子,这个盒子里封装了一个值;想要处理盒子中的值,我们需要给盒子的map方法传递一个处理值的函数(要是纯函数),由这个函数来对值进行处理。

最终map方法返回一个包含新值的盒子(函子)

Functor 的问题

//传入null和undefined的问题
Container.of(null).map(x => x.toUpperCase())//会报错,相同的输入无法相同的输出,就不是纯函数了

MayBe 函子处理空值问题

在编程过程中可能遇到很多错误,需要对这些错误做相应的处理。MayBe 函子的作用就是可以对外部的空值情况做处理(控制副作用在允许的范围 )

异常会让函数变的不纯。

可以解决空值的问题,多次调用map方法,不知道哪里出了问题。

class MayBe {
    static of(value) {
        return new MayBe(value);
    }
    constructor(value){
        this._value = value;
    }
    map(fun) {
        return this.isNothing(this._value) ? MayBe.of(null) : MayBe.of(fun(this._value))
    }
    isNothing(value) {
        return value === null || value === undefined;
    }
}
//const r = MayBe.of('hello world').map(x => x.toUpperCase())
//console.log(r)

//const r = MayBe.of().map(x => x.toUpperCase()) //不传值就是undefined
//console.log(r)

// 连续调用出现错误无法定位
const r = MayBe.of('hello world').map(x => x.toUpperCase()).map(x => null).map(x => x.split(' '))
console.log(r) //打印null,不知是哪个函数导致的,就很难定位问题。

Either函子 定位具体错误位置

解决MayBe处理完错误,却不知问题具体在哪,只会返回一个null的函子问题。

Either 函子可以用来处理异常处理,Either类似if else的处理,并记录错误的信息。

//left 记录异常类
class Left {
    static of(value) {
        return new Left(value);
    }
    constructor(value){
        this._value = value;
    }
    map(fun) {
        return this;
    }
}
// right 处理正常
class Right {
    static of(value){
        return new Right(value);
    }
    constructor(value){
        this._value = value;
    }
    map(fun){
        return new Right(fun(this._value))
    }
}

//实现一个字符串转对象的方法
function parseJSON(str) {
    try{
        return Right.of(JSON.parse(str));
    } catch(e) {
        return Left.of({name:e.name, message:e.message})
    }
}
parseJSON('{name:123}')//包含异常错误的函子 _value: {name: "SyntaxError", message: "Unexpected token n in JSON at position 1"}
parseJSON('{"name":"123"}')//_value: {name: "123"} 的函子对象

IO函子将不纯函数交给调用者处理

_value 在IO函的constructor构造器里 是作为一个函数,

IO 函子可以把不纯的函数存到_value中,延迟执行这个不纯的操作,把不纯的操作交给调用者来处理。

/**
 * process 是node中的进程对象 http://nodejs.cn/api/process.html
 * execPath 是node中process进程对象的 可执行文件的绝对路http://nodejs.cn/api/process.html#process_process_execpath
 */
const fp = require('lodash/fp')
class IO {
    static of (value) { //1、接受node 中的process对象,做为传入IO函子的返回值。
        return new IO(function() { 
            return value 
        });
    }
    constructor(fun){//2、IO函子 接收一个返回值是process的函数作为_value的值
        this._value = fun; 
    }
    map(fun) {//3、将IO函数接受到的不纯函数执行返回 process对象给到fun这个函数,fun这个函数将process对象的execPath函数的结果给到 fp.flowRight这个函数组合,函数组合把这个结果给到新的函子,
        return new IO(fp.flowRight(fun, this._value));
    }
}
const r = IO.of(process).map(p => p.execPath); //4、r拿到新的函子对象
console.log(r._value())//5、将放在_value的不纯函数执行

IO函子问题

IO函子嵌套IO函子的问题

//IO 函子嵌套太深的问题,IO(IO(X)) 一个IO包裹一个IO
const fs = require('fs');
const fp = require('lodash/fp');
class IO {
    static of(value) {
        return new IO(value);
    }
    constructor(fun) {
        this._value = fun;
    }
    map(fun) {
        return new IO(fp.flowRight(fun, this._value));
    }
}

function readFile(fileName) {
    return new IO(function() {
        console.log('我是第一个函数的函子')
        return fs.readFileSync(fileName, 'utf-8')
    })
}
function cole(value) {
    return new IO(function() {
        console.log('我是第二个函数的函子;')
        console.log("value:-------------------")
        console.log(value); //第一个函数返回的函子
        console.log(value._value()); //第一个函的内容
        console.log('---------------------');
        return value; //返回的 flowRight 组合函数中readFile函数执行返回的函子,赋值给了cole的value
    })
}

const r = fp.flowRight(cole, readFile);
r('package.json')._value() //第二个函数cole的函子函数
console.log('---------')
//每次点value嵌套很不爽。
r('package.json')._value()._value() //我是第一个函数readFile的函子

Monad函子 解决IO函子嵌套问题

或者叫IO Monad,一个具有静态的IO方法和join方法的一个函子,可以解决函子嵌套问题,当一个函数返回一个函子的时候,就可以使用Monad函子,当返回的是一个值,就可以使用map方法

const fs = require('fs');
const fp = require('lodash/fp');
class IO {
    static of(value) {
        return new IO(value);
    }
    constructor(fun) {
        this._value = fun;
    }
    map(fun) {
        return new IO(fp.flowRight(fun, this._value));
    }
    
    join() {
        this._value()
    }
    flatMap(fun){//我的理解这个函数更像一个代理执行map函数;接受fun,是因为map需要接受一个函数
        this.map(fun).join();//将map执行返回的io函数使用join拍平
    }
}

function readFile(fileName) {
    return new IO(function() {
        return fs.readFileSync(fileName, 'utf-8')
    })
}
function cole(value) {
    return new IO(function() {
        console.log(value); 
        return value;
    })
}

const r = readFile('package.json');
//r.map() //当组合函数返回的是一个值的时候使用map
//r.flatMap()//当组合函数返回的是一个函子的时候使用flatMap
//因为返回的是个函子所以使用flatMap
r.flatMap(cole).join()
console.log(r)

Folktale 函数式编程库

Task异步执行

异步任务的实现过程过于复杂,我们使用folktale中的Task 来演示。

folktale 一个标准的函数式编程库:和lodash、ramda不同的是,他没有提供很多功能函数。

只提供了一些函数式处理的操作,例如:compose、curry等,一些函子 Task、Either、MayBe等

folktale的安装 https://folktale.origamitower.com/docs/v2.0.0/download/

compose和curry的使用

文档:https://folktale.origamitower.com/api/v2.3.0/en/folktale.core.lambda.compose.compose.html

Core.Lambal 提供转换和组合函数的工具:

Core.Lambal入口:在上面的folktale的官网进去:API reference => core => lambda【到了这里就是lambda的文档】 => Function composition(compose和curry都在里面)

Lambal快速入口地址:https://folktale.origamitower.com/api/v2.3.0/en/folktale.core.lambda.html

folktale中的curry函数柯里化

curry参数:

第一个参:柯里化函数预期的参数数量,其实就是上面我学到的 函数名点length 拿到形参列表,当实参少于预期参数数量就是少于这个函数名点length的形参长度,就返回函数;如果预期参数的数量小于实际的形参列表 函数名点length ,会照成计算错误,柯里化函数会认为参数到位了,计算然后报错。

第二个参数:就是函数柯里化的接受的函数,手机这些参数时应调用的函数,就是上面手写柯里化函数传的参数。

总结:就是多了一个预期参数数量,不用使用 函数名点length ,记住参数数量要跟传的参数一样,多了返回返回函数,少了报错跟逻辑有关(我这代码块少了就报错)

const {curry} = require("folktale/core/lambda");
const f = curry(2, (x, y) => {
    return x * y;
}) 
console.log(f(1,2))
console.log(f(1)(2))
folktale 中的compose函数组合

compose 跟lodash 中fp模块的 flowRight组合函数一样默认从右开始

const {compose} = require("folktale/core/lambda");
const m2 = (x) => x * 2;
const m3 = (x) => x * x;
const f = compose(m3, m2);
console.log(f(2)) // 16
Task函子

https://folktale.origamitower.com/api/v2.3.0/en/folktale.concurrency.task.html

一种模拟异步操作的数据结构,支持安全取消和自动资源处理。

此 API 仍处于实验阶段,因此可能会在未来版本中更改或删除。您不应该将它用于生产应用程序。

const fs = require("fs");
const { task } = require("folktale/concurrency/task");
const { split, find } = require("lodash/fp");
function readFile(fileUrl) {
    return task((resolver) => {
        //异步读取文件【文件路径,编码,回调函数(错误优先所以err)】
        fs.readFile(fileUrl, 'utf-8', (err,data) => {
            // if(err) resolver.reject(err); //表示执行失败,提供失败原因
            // if(data) resolver.resolve(data); //表示执行成功,提供返回值
            
            //取消代码不能下面执行一起使用
            // resolver.cancel() //取消任务
            
            //简写 执行成功和失败
            err ? resolver.reject(err) : resolver.resolve(data)
        })
    })
}

.run() 运行任务会返回一个 TaskExecution 对象,这个对象允许取消任务执行,或者作为 JavaScript 的 Promise 或 Folktale 的 Future 查询其最终值:【但不支持嵌套promimse,并且取消作为拒绝处理】

const execution = readFile('package.json').run();
async function helloExecution(){
    return await execution.promise();
}
var value = helloExecution()
console.log(value)

使用 listen() 方法 对运行任务的结果做出反应

const execution = readFile('package.json').run();
execution.listen({
	onCancelled: () => {  //取消的
        console.log('取消任务')
    },
	onResolved:(value) => { //执行成功的回调
		console.log('任务成功',value)
	},
	onRejected:(reason) => {  //失败的
		console.log('失败原因', reason)
	}
})
let hello = readFile('package.json');//返回一个函子,所有的函子都有一个map方法
hello = hello.map(split("\n")) //在map里对结果值进行处理,切割
hello = hello.map(find(value => value.includes('folktale')))
let execution = hello.run() //运行任务
execution.listen({
	onCancelled: () => {  //取消的
        console.log('取消任务')
    },
	onResolved:(value) => { //执行成功的回调
		console.log('任务成功',value)
	},
	onRejected:(reason) => {  //失败的
		console.log('失败原因', reason)
	}
})

//简写
readFile('package.json').map(split("\n")).map(find(value => value.includes('folktale'))).run().listen({
	onCancelled: () => {  //取消的
        console.log('取消任务')
    },
	onResolved:(value) => { //执行成功的回调
		console.log('任务成功',value)
	},
	onRejected:(reason) => {  //失败的
		console.log('失败原因', reason)
	}
})

Pointed函子

就是实现了of方法的函子,of方法是用来避免使用new 来创建对象,更深的含义是用of方法把值放到上下文Context中(把值放到容器中,使用map来处理值)。

class Container {
	static of(value) { //of方法是把新的值包裹的函子里面返回
        return new Container(value); //返回的结果就是一个上下文
	}
}
Container.of(2).map()//在上下文就可以处理这个数据