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)

    何为Curry化/柯里化? curry化来源与数学家 Haskell Curry的名字 (编程语言 Haskell也是以他的名字命名). 柯里化通常也称部分求值,其含义是给函数分步传递参数,每次传递参 ...

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

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

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

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

  4. JS中的柯里化及精巧的自动柯里化实现

    一.什么是柯里化? 在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术.这个技术由 C ...

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

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

  6. 5.0 JS中引用类型介绍

    其实,在前面的"js的六大数据类型"文章中稍微说了一下引用类型.前面我们说到js中有六大数据类型(五种基本数据类型 + 一种引用类型).下面的章节中,我们将详细讲解引用类型. 1. ...

  7. 【repost】JS中的异常处理方法分享

    我们在编写js过程中,难免会遇到一些代码错误问题,需要找出来,有些时候怕因为js问题导致用户体验差,这里给出一些解决方法 js容错语句,就是js出错也不提示错误(防止浏览器右下角有个黄色的三角符号,要 ...

  8. JS中给正则表达式加变量

    前不久同事询问我js里面怎么给正则中添加变量的问题,遂写篇博客记录下.   一.字面量 其实当我们定义一个字符串,一个数组,一个对象等等的时候,我们习惯用字面量来定义,例如: var s = &quo ...

  9. js中几种实用的跨域方法原理详解(转)

    今天研究js跨域问题的时候发现一篇好博,非常详细地讲解了js几种跨域方法的原理,特分享一下. 原博地址:http://www.cnblogs.com/2050/p/3191744.html 下面正文开 ...

随机推荐

  1. MyBatis动态SQL(使用)整理

    MyBatis 令人喜欢的一大特性就是动态 SQL.在使用 JDBC 的过程中, 根据条件进行 SQL 的拼接是很麻烦且很容易出错的.MyBatis 动态 SQL 的出现, 解决了这个麻烦. MyBa ...

  2. touchstart 事件与 click 事件的冲突

    const clickEvent = (function() {   if ('ontouchstart' in document.documentElement === true)     retu ...

  3. jenkins iOS自动打包

    1.Jenkins配置 采用命令行下载配置Jenkins,防止产生权限问题 1)先安装brew,打开命令行,输入:/usr/bin/ruby -e "$(curl -fsSL https:/ ...

  4. [Java并发编程之美]第1章 线程基础 补充知识

    1.2线程创建与运行 创建线程有三种方式: 继承Thread类并重写run方法: 实现Runnable接口的run方法,new Thread时将该类对象作为参数传入: 实现Callable接口的cal ...

  5. Docker实战(4):Docker错误记一笔

    创建容器的时候报错WARNING: IPv4 forwarding is disabled. Networking will not work. 解决办法: vim /usr/lib/sysctl.d ...

  6. 高可用集群之keepalived+lvs实战

    keepalived简介 lvs在我之前的博客<高负载集群实战之lvs负载均衡-技术流ken>中已经进行了详细的介绍和应用,在这里就不再赘述.这篇博文将把lvs与keepalived相结合 ...

  7. bootsrap 样式笔记

    颜色 --blue: #007bff; --indigo: #6610f2; --purple: #6f42c1; --pink: #e83e8c; --red: #dc3545; --orange: ...

  8. 使用Mysql分区表对数据库进行优化

    早期工作中没有做好足够的设计,目前记录表单表数据2000w且无有效索引,表现是分页缓慢,模糊查询拉闸. 当前业务中,写操作会多于读操作,时不时会遇到慢SQL占用过多的数据连接,导致写操作无法正常进行. ...

  9. hystrix文档翻译之metrics

     metrics和监控 动机 HystrixCommands和HystrixObservableCommands执行过程中会产生相关运行情况的metrics.这些metrics对于监控系统表现有很大的 ...

  10. Spring Boot实战之Redis缓存登录验证码

    1.工具类 1 import lombok.experimental.UtilityClass; 2 3 import java.awt.*; 4 import java.awt.image.Buff ...