掘金“最棒的”柯里化(curry)指南 | 函数式编程
2020/2/25 11:15:37
本文主要是介绍掘金“最棒的”柯里化(curry)指南 | 函数式编程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
在面试中经常会遇见面试官问你了解过柯里化吗?或者接触过高阶函数吗?亦或是能写一个curry工具函数把普通的函数变成高阶函数吗?不知道你们遇到这些问题是对答如流还是一脸懵逼,不过都没关系,看完这篇文章想必你一定能掌握柯里化相关的知识技能。
文章分三块,第一块讲柯里化的概念,第二块讲柯里化的实现,第三块举一个实际项目中的例子帮助大家了解其优点并运用柯里化。
柯里化的概念
概念:柯里化的概念只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。 例子: 下面实现一个输入dom元素数组返回元素子元素数组的函数。
- 正常
// Object.prototype.toString.call(elements) === "[object Array]" function getChildren(elements){ return elements.map(i => i.childNodes) } 复制代码
- curry
// 先假设我们实现了柯里化函数 curry, 下面会讲curry的实现细节 // curry: (* → a) → (* → a) //function curry(fn: (any)=> any):(any)=>any{} //var map = curry(function(f, ary) { // return ary.map(f); //}); let getChildren = map(i => i.childNodes) 复制代码
上面的2种实现方式的效果是一样的,第一种方式直接定义了操作数组的函数,第二种方式不直接定义操作数组的函数,而是调用map函数,内联i=>i.childNodes
返回新的函数。
柯里化实现
下面介绍2种方式,第一种实现方式相比于第二种最明显的缺陷是经过柯里化后不能获取函数的参数的个数, 比如:
function a(b1, b2){} console.log(a.length) // 2 // 柯里化后 let curryA = curry(a) console.log(curryA.length) // 等下如果使用第一种实现,length = 0 复制代码
第一种实现
function curry(fn){ return function f(){ const args = [].slice.call(arguments) if(args.length < fn.length){ return function(){ // 下面的arguments与上面的arguments不同 return f.apply(this, args.concat([].slice.call(arguments))) } }else{ return fn.apply(this, args) } } } 复制代码
第二种实现
为了弥补不能获取函数参数的长度需要借助一个辅助函数,辅助函数包裹目标函数,下面的实现参考了ramda的源码,剔除了占位符的功能,该辅助函数的参数只适用于 0 - 10 个,多于10个会报错。
// n为还需接收的参数 var _arity = function (n, fn) { /* eslint-disable no-unused-vars */ switch (n) { case 0: return function() { return fn.apply(this, arguments); }; case 1: return function(a0) { return fn.apply(this, arguments); }; case 2: return function(a0, a1) { return fn.apply(this, arguments); }; case 3: return function(a0, a1, a2) { return fn.apply(this, arguments); }; case 4: return function(a0, a1, a2, a3) { return fn.apply(this, arguments); }; case 5: return function(a0, a1, a2, a3, a4) { return fn.apply(this, arguments); }; case 6: return function(a0, a1, a2, a3, a4, a5) { return fn.apply(this, arguments); }; case 7: return function(a0, a1, a2, a3, a4, a5, a6) { return fn.apply(this, arguments); }; case 8: return function(a0, a1, a2, a3, a4, a5, a6, a7) { return fn.apply(this, arguments); }; case 9: return function(a0, a1, a2, a3, a4, a5, a6, a7, a8) { return fn.apply(this, arguments); }; case 10: return function(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) { return fn.apply(this, arguments); }; default: throw new Error('First argument to _arity must be a non-negative integer no greater than ten'); } } 复制代码
下面是实现,具体可以看注释:
// curry使用_curry1柯里化,接收参数fn, 默认调用curryN, // curry = (fn) => curryN(fn.length, fn) var curry = _curry1(function(fn) { return curryN(fn.length, fn); }); // 如果是一个参数,使用_curry1, ramda里面的内部函数,接收的参数大多为1-3个 var _curry1 = function (fn) { return function f1(a) { if (arguments.length === 0) { return f1; } else { return fn.apply(this, arguments); } }; } // 如果参数是2个 _curry2 = function(fn) { return function f2(a, b){ switch(arguments.length){ // 返回本身 case 0: return f2; // 返回_curry1的结果, case 1: return _curry1(function(_b) { return fn(a, _b); }) // 直接调用 case 2: return fn(a, b) } } } // curryN本身也是一个柯里化的函数 var curryN = _curry2(function(n, fn){ if (length === 1) { return _curry1(fn); } // 使用_artity包裹函数_curryN, _curryN包括3个参数,具体看下面的实现 return _arity(length, _curryN(length, [], fn)); }) // 内部_curryN的实现, 在上面第一种方式,的基础上添加包裹函数 var _curryN = function (length, recived, fn) { return function() { // 获取函数调用的参数 var args = [].slice.call(arguments); // 已传的参数 var combined = recived.concat(args); if(combined.length < length ) { return _arity(length - combined.length, _curryN(length, combined, fn)); } else { return fn.apply(this, combined); } } } 复制代码
柯里化优点
优点:只传给函数一部分参数通常叫做局部调用(partial application),这样做的好处缓存参数,能够大量减少样板文件代码(boilerplate code)。(下面的例子中可以看到的)
在实际的项目中,我们调用后端的接口通常是一个函数对于一个接口,通过执行函数获得后端结果的返回数据,其中的问题就是每个函数的内容差不多,也就是样板代码多,比如下面这张图:
我们完成可以通过柯里化做成这样:参考文档
- 基础知识,我参考了这里 js函数式编程指南
- ramda的源码解析,我参考了这篇掘金文章Ramda.js中的柯里化实现
- ramda的源码链接curry.js
这篇关于掘金“最棒的”柯里化(curry)指南 | 函数式编程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-04el-table 开启定时器下,表格的选中状态会消失是什么原因-icode9专业技术文章分享
- 2024-10-03如何安装和初始化飞牛私有云 fnOS?-icode9专业技术文章分享
- 2024-10-03如何安装 App 并连接到飞牛 NAS?-icode9专业技术文章分享
- 2024-10-03如何安装飞牛 TV 并连接到影视服务器?-icode9专业技术文章分享
- 2024-10-03如何在PVE和ESXI上安装飞牛私有云 fnOS?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS安装系统异常情况处理-icode9专业技术文章分享
- 2024-10-03飞牛NAS如何创建存储空间?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS硬盘会自动休眠吗?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS如何安装飞牛影视和创建媒体库?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS如何为家人朋友开通影视账号?-icode9专业技术文章分享