Js中Currying的应用

柯里化Currying是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术,是函数式编程应用。

描述

如果说函数式编程中有两种操作是必不可少的那无疑就是柯里化Currying和函数组合Compose,柯里化其实就是流水线上的加工站,函数组合就是我们的流水线,它由多个加工站组成。对于加工站即柯里化Currying,简单来说就是将一个多元函数,转换成一个依次调用的单元函数,也就是把一个多参数的函数转化为单参数函数的方法,函数的柯里化是用于将一个操作分成多步进行,并且可以改变函数的行为,在我的理解中柯里化实际就是实现了一个状态机,当达到指定参数时就从继续接收参数的状态转换到执行函数的状态。
简单来说,通过柯里化可以把函数调用的形式改变。

f(a,b,c) → f(a)(b)(c)

与柯里化非常相似的概念有部分函数应用Partial Function Application,这两者不是相同的,部分函数应用强调的是固定一定的参数,返回一个更小元的函数。

// 柯里化
f(a,b,c) → f(a)(b)(c)
// 部分函数调用
f(a,b,c) → f(a)(b,c) / f(a,b)(c)

柯里化强调的是生成单元函数,部分函数应用的强调的固定任意元参数,而我们平时生活中常用的其实是部分函数应用,这样的好处是可以固定参数,降低函数通用性,提高函数的适合用性,在很多库函数中curry函数都做了很多优化,已经不是纯粹的柯里化函数了,可以将其称作高级柯里化,这些版本实现可以根据你输入的参数个数,返回一个柯里化函数/结果值,即如果你给的参数个数满足了函数条件,则返回值。

实现

实现一个简单的柯里化的函数,可以通过闭包来实现。

var add = function(x) {return function(y) {return x + y;};
};
console.log(add(1)(2)); // 3

当有多个参数时,这样显然不够优雅,于是封装一个将普通函数转变为柯里化函数的函数。

function convertToCurry(funct, ...args) {const argsLength = funct.length;return function(..._args) {_args.unshift(...args);if (_args.length < argsLength) return convertToCurry.call(this, funct, ..._args);return funct.apply(this, _args);}
}var funct = (x, y, z) => x + y + z;
var addCurry = convertToCurry(funct);
var result = addCurry(1)(2)(3);
console.log(result); // 6

举一个需要正则匹配验证手机与邮箱的例子来展示柯里化的应用。

function convertToCurry(funct, ...args) {const argsLength = funct.length;return function(..._args) {_args.unshift(...args);if (_args.length < argsLength) return convertToCurry.call(this, funct, ..._args);return funct.apply(this, _args);}
}var check = (regex, str) =>  regex.test(str);
var checkPhone = convertToCurry(check, /^1[34578]\d{9}$/);
var checkEmail = convertToCurry(check, /^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);
console.log(checkPhone("13300000000")); // true
console.log(checkPhone("13311111111")); // true
console.log(checkPhone("13322222222")); // true
console.log(checkEmail("13300000000@a.com")); // true
console.log(checkEmail("13311111111@a.com")); // true
console.log(checkEmail("13322222222@a.com")); // true

应用

高级柯里化有一个应用方面在于Thunk函数,Thunk函数是应用于编译器的传名调用实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体,这个临时函数就叫做Thunk 函数。Thunk函数将参数替换成单参数的版本,且只接受回调函数作为参数。

// 假设一个延时函数需要传递一些参数
// 通常使用的版本如下
var delayAsync = function(time, callback, ...args){setTimeout(() => callback(...args), time);
}var callback = function(x, y, z){console.log(x, y, z);
}delayAsync(1000, callback, 1, 2, 3);// 使用Thunk函数var thunk = function(time, ...args){return function(callback){setTimeout(() => callback(...args), time);}
}var callback = function(x, y, z){console.log(x, y, z);
}var delayAsyncThunk = thunk(1000, 1, 2, 3);
delayAsyncThunk(callback);

实现一个简单的Thunk函数转换器,对于任何函数,只要参数有回调函数,就能写成Thunk函数的形式。

var convertToThunk = function(funct){return function (...args){return function (callback){return funct.apply(this, args);}};
};var callback = function(x, y, z){console.log(x, y, z);
}var delayAsyncThunk = convertToThunk(function(time, ...args){setTimeout(() => callback(...args), time);
});thunkFunct = delayAsyncThunk(1000, 1, 2, 3);
thunkFunct(callback);

Thunk函数在ES6之前可能应用比较少,但是在ES6之后,出现了Generator函数,通过使用Thunk函数就可以可以用于Generator函数的自动流程管理。首先是关于Generator函数的基本使用,调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的迭代器iterator对象,他是一个指向内部状态对象的指针。当这个迭代器的next()方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield后紧跟迭代器要返回的值,也就是指针就会从函数头部或者上一次停下来的地方开始执行到下一个yield。或者如果用的是yield*,则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。

function* f(x) {yield x + 10;yield x + 20;return x + 30;
}
var g = f(1);
console.log(g); // f {<suspended>}
console.log(g.next()); // {value: 11, done: false}
console.log(g.next()); // {value: 21, done: false}
console.log(g.next()); // {value: 31, done: true}
console.log(g.next()); // {value: undefined, done: true} // 可以无限next(),但是value总为undefined,done总为true

由于Generator函数能够将函数的执行暂时挂起,那么他就完全可以操作一个异步任务,当上一个任务完成之后再继续下一个任务,下面这个例子就是将一个异步任务同步化表达,当上一个延时定时器完成之后才会进行下一个定时器任务,可以通过这种方式解决一个异步嵌套的问题,例如利用回调的方式需要在一个网络请求之后加入一次回调进行下一次请求,很容易造成回调地狱,而通过Generator函数就可以解决这个问题,事实上async/await就是利用的Generator函数以及Promise实现的异步解决方案。

var it = null;function f(){var rand = Math.random() * 2;setTimeout(function(){if(it) it.next(rand);},1000)
}function* g(){ var r1 = yield f();console.log(r1);var r2 = yield f();console.log(r2);var r3 = yield f();console.log(r3);
}it = g();
it.next();

虽然上边的例子能够自动执行,但是不够方便,现在实现一个Thunk函数的自动流程管理,其自动帮我们进行回调函数的处理,只需要在Thunk函数中传递一些函数执行所需要的参数比如例子中的index,然后就可以编写Generator函数的函数体,通过左边的变量接收Thunk函数中funct执行的参数,在使用Thunk函数进行自动流程管理时,必须保证yield后是一个Thunk函数。
关于自动流程管理run函数,首先需要知道在调用next()方法时,如果传入了参数,那么这个参数会传给上一条执行的yield语句左边的变量,在这个函数中,第一次执行next时并未传递参数,而且在第一个yield上边也并不存在接收变量的语句,无需传递参数,接下来就是判断是否执行完这个生成器函数,在这里并没有执行完,那么将自定义的next函数传入res.value中,这里需要注意res.value是一个函数,可以在下边的例子中将注释的那一行执行,然后就可以看到这个值是f(funct){...},此时我们将自定义的next函数传递后,就将next的执行权限交予了f这个函数,在这个函数执行完异步任务后,会执行回调函数,在这个回调函数中会触发生成器的下一个next方法,并且这个next方法是传递了参数的,上文提到传入参数后会将其传递给上一条执行的yield语句左边的变量,那么在这一次执行中会将这个参数值传递给r1,然后在继续执行next,不断往复,直到生成器函数结束运行,这样就实现了流程的自动管理。

function thunkFunct(index){return function f(funct){var rand = Math.random() * 2;setTimeout(() => funct({rand:rand, index: index}), 1000)}
}function* g(){ var r1 = yield thunkFunct(1);console.log(r1.index, r1.rand);var r2 = yield thunkFunct(2);console.log(r2.index, r2.rand);var r3 = yield thunkFunct(3);console.log(r3.index, r3.rand);
}function run(generator){var g = generator();var next = function(data){var res = g.next(data);if(res.done) return ;// console.log(res.value);res.value(next);}next();
}run(g);

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://www.jianshu.com/p/5e1899fe7d6b
https://zhuanlan.zhihu.com/p/108594470
https://juejin.im/post/6844903936378273799#heading-12
https://blog.csdn.net/crazypokerk_/article/details/97674338
http://www.qiutianaimeili.com/html/page/2019/05/54g0vvxycyg.html
https://baike.baidu.com/item/%E6%9F%AF%E9%87%8C%E5%8C%96/10350525?fr=aladdin
https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/ch4.html#%E4%B8%8D%E4%BB%85%E4%BB%85%E6%98%AF%E5%8F%8C%E5%85%B3%E8%AF%AD%E5%92%96%E5%96%B1

Js中Currying的应用相关推荐

  1. JS中的柯里化(currying) 转载自张鑫旭-鑫空间-鑫生活[http://www.zhangxinxu.com]

    JS中的柯里化(currying) by zhangxinxu from http://www.zhangxinxu.com 本文地址:http://www.zhangxinxu.com/wordpr ...

  2. Js中函数式编程的理解

    函数式编程的理解 函数式编程是一种编程范式,可以理解为是利用函数把运算过程封装起来,通过组合各种函数来计算结果.函数式编程与命令式编程最大的不同其实在于,函数式编程关心数据的映射,命令式编程关心解决问 ...

  3. 【转载】JS中bind方法与函数柯里化

    原生bind方法 不同于jQuery中的bind方法只是简单的绑定事件函数,原生js中bind()方法略复杂,该方法上在ES5中被引入,大概就是IE9+等现代浏览器都支持了(有关ES5各项特性的支持情 ...

  4. js中函数对象的方法,原型方法apply、call、bind、toString、toLocaleString、valueOf

    全栈工程师开发手册 (作者:栾鹏) js系列教程4-函数.函数参数教程全解 js中函数也是一种对象,因此有自己的原型对象,可以作为其他对象的属性,也可以作为其他函数的参数. 函数方法 [apply() ...

  5. 从λ演算到函数式编程聊闭包(1):闭包概念在Java/PHP/JS中形式

    什么是闭包 如果让谷哥找一下"闭包"这个词,会发现网上关于闭包的文章已经不计其数 维基百科上对闭包的解释就很经典: 在计算机科学中,闭包(Closure)是词法闭包(Lexical ...

  6. 在js中使用HashMap数据结构,在js中使用K,V数据结构

    首先是定义一个HashMap方法,做基类(复制在js中即可,然后引用) //简单的哈希表,begin function HashMap() {/** Map 大小 * */var size = 0;/ ...

  7. [JavaScript] 探索JS中的函数秘密

    函数长啥样? 把一些要重复使用的内容封装到函数内. function foo(title) {console.log(title) } foo('title') foo('dust') foo('he ...

  8. 在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查

    Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用JavaScript开发并且能够用在Node.JS环境中,易于使用,支持多SQL方言(dialect),.它当前支持M ...

  9. 在JS中最常看到切最容易迷惑的语法(转)

    发现一篇JS中比较容易迷惑的语法的解释,挺有用的,转载下,与大家分享: js中大括号有四种语义作用 语义1,组织复合语句,这是最常见的 Js代码  if( condition ) { //... }e ...

最新文章

  1. 导出excel 数字前少0_【产品介绍】数字压力校验仪
  2. 记录一次提交开源JAR包到中央仓库的过程
  3. Linux2.6内核--中断下半部实现方法 工作队列
  4. Ubuntu 相关命令行工具
  5. 建智能工厂,可从这6个方面着手!
  6. Make Membership header extension fields editable
  7. nosql非关系型数据库_从Datomic出发,革命性的非NoSQL数据库
  8. ASP渲染下拉框使时间依次减少
  9. php找出函数定义位置,WordPress如何快速定位PHP函数所在文件位置及代码行号?
  10. 如何修改操作系统运行服务器,如何设置Bios 最常见bios设置与修改详细图解教程...
  11. shell echo 彩色字体
  12. BZOJ4029: [HEOI2015]定价
  13. PyTorch并行与分布式(三)DataParallel原理、源码解析、举例实战
  14. Spring Boot集成Spring Data Reids和Spring Session实现Session共享(多个不同的应用共用一个Redis实例)...
  15. Item 22: 当使用Pimpl机制时,在实现文件中给出特殊成员函数的实现
  16. Linux下make -j加快编译速度
  17. QT5.11安装教程
  18. EURA欧瑞E1000系列变频器使用PID实现恒压供水功能的相关参数设置及接线
  19. 中央财经大学研究生入学考试 901C语言程序设计
  20. java蓝桥杯练习 调和数列问题

热门文章

  1. H264帧的分析sps pps
  2. redis linux工具安装,redis Linux版本的安装,以及一些基本的认识
  3. linux httpd 开机启动脚本,httpd服务如何开机启动
  4. JAVA IO基本知识
  5. Free Mybatis plugin
  6. saltstack案例 自助机
  7. 借助精益找回敏捷的质量
  8. 《R语言机器学习:实用案例分析》——1.2节R的数据结构
  9. tcp协议seq和ack
  10. RDLC 格式化文本内容