什么是函数式编程?
函数式编程(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这个管道足够长,出现了问题很难找到出现问题的地方。
当fun函数笔记复杂的时候,可以把函数fn拆分成多给小的函数,此时多了中间运算过程产生的m和n。
可以把fn这个管道拆分成了3个管道f1,f2,f3,数据a通过管道f3得到结果m,m在通过管道f2得到结果n,n通过管道f1 得到最终结果b。
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'))
执行结果:可以通过插入函数拿到上个函数的结果找出问题.
使用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()//在上下文就可以处理这个数据