函数柯里化 | 原理、实现与应用

张开发
2026/4/12 12:13:28 15 分钟阅读

分享文章

函数柯里化 | 原理、实现与应用
注本文为 “函数柯里化” 相关合辑。略作重排未整理去重。如有内容异常请看原文。柯里化函数Currying定义与应用场景wincheshe 原创于 2022-03-08 17:43:25 发布柯里化是将多参数函数转换为一系列单参数函数的技术支持分步传入参数并生成偏函数。在 JavaScript 环境中柯里化可简化函数调用形式例如将sum(a, b, c)转换为sum(a)(b)(c)。该技术可提升代码模块化程度与复用性典型场景为日志记录场景下便捷日志函数的构建。高阶柯里化实现如 lodash 库中的_.curry同时支持常规调用与偏函数调用。柯里化适用于参数数量固定的函数不适用于包含剩余参数rest 参数的函数。柯里化Currying柯里化Currying 属于函数相关的高阶技术其应用范围不限于 JavaScript同时覆盖其他编程语言。柯里化是一类函数转换操作可将调用形式为f ( a , b , c ) f(a, b, c)f(a,b,c)的函数转换为调用形式为f ( a ) ( b ) ( c ) f(a)(b)(c)f(a)(b)(c)的函数。柯里化不执行函数调用仅对函数结构进行转换。通过示例可直观理解该转换逻辑随后结合实际场景说明其应用价值。构建辅助函数curry(f)实现对双参数函数f ff的柯里化转换。即对双参数函数f ( a , b ) f(a, b)f(a,b)执行curry(f)后可通过f ( a ) ( b ) f(a)(b)f(a)(b)形式完成调用functioncurry(f){// curry(f) 执行柯里化转换returnfunction(a){returnfunction(b){returnf(a,b);};};}// 用法functionsum(a,b){returnab;}letcurriedSumcurry(sum);alert(curriedSum(1)(2));// 3上述实现包含两层包装结构curry(func)的返回值为包装函数function(a)。以curriedSum(1)形式调用时参数被保存至词法环境并返回新的包装函数function(b)。后续以参数2 22调用该包装函数时将执行流程传递至原始sum函数。高阶柯里化实现如 lodash 库中的 _.curry返回的包装函数同时支持常规调用与偏函数调用functionsum(a,b){returnab;}letcurriedSum_.curry(sum);// 使用来自 lodash 库的 _.curryalert(curriedSum(1,2));// 3常规调用alert(curriedSum(1)(2));// 3偏函数调用柯里化的应用价值通过实际工程场景说明柯里化的优势。定义日志格式化与输出函数log(date, importance, message)。实际项目中该类函数可实现日志网络传输等扩展功能此处以alert简化实现functionlog(date,importance,message){alert([${date.getHours()}:${date.getMinutes()}] [${importance}]${message});}对该函数执行柯里化转换log_.curry(log);柯里化后log可维持原有调用方式log(newDate(),DEBUG,some debug);// log(a, b, c)同时支持柯里化调用形式log(newDate())(DEBUG)(some debug);// log(a)(b)(c)基于柯里化可快速构建便捷日志函数// logNow 为固定首个参数的偏函数letlogNowlog(newDate());// 使用logNow(INFO,message);// [HH:mm] INFO messagelogNow为固定首个参数的log函数属于偏应用函数。进一步构建当前调试日志专用函数letdebugNowlogNow(DEBUG);debugNow(message);// [HH:mm] DEBUG message结论柯里化后原函数调用能力保持完整log可正常执行。可快速生成各类偏函数例如固定日期的日志函数。高阶柯里化实现适用于多参数函数的通用高阶柯里化实现如下可兼容前述示例场景functioncurry(func){returnfunctioncurried(...args){if(args.lengthfunc.length){returnfunc.apply(this,args);}else{returnfunction(...args2){returncurried.apply(this,args.concat(args2));};}};}使用示例functionsum(a,b,c){returnabc;}letcurriedSumcurry(sum);alert(curriedSum(1,2,3));// 6常规调用alert(curriedSum(1)(2,3));// 6分步传入参数alert(curriedSum(1)(2)(3));// 6完整柯里化调用curry(func)返回的包装函数curried结构如下// func 为待转换目标函数functioncurried(...args){if(args.lengthfunc.length){returnfunc.apply(this,args);}else{returnfunction(...args2){returncurried.apply(this,args.concat(args2));};}};执行流程分为两类分支传入参数数量大于或等于原函数形参数量func.length时通过func.apply直接执行原函数。传入参数数量不足时返回新包装函数合并历史参数与新增参数后递归调用curried。重复调用后若参数数量仍不满足则继续返回偏函数参数齐全后执行原函数并返回结果。适用范围约束柯里化要求目标函数具备固定数量的形参。包含剩余参数的函数如f(...args)不适用该类柯里化实现。扩展实现说明标准定义下柯里化将s u m ( a , b , c ) sum(a, b, c)sum(a,b,c)转换为s u m ( a ) ( b ) ( c ) sum(a)(b)(c)sum(a)(b)(c)。JavaScript 环境中主流柯里化实现为扩展形式支持多参数组合调用方式。总结柯里化是一类函数转换操作可将调用形式f ( a , b , c ) f(a,b,c)f(a,b,c)转换为f ( a ) ( b ) ( c ) f(a)(b)(c)f(a)(b)(c)。JavaScript 环境中的主流实现保留原函数调用方式参数数量不足时返回偏函数。柯里化可简化偏函数的构建流程。日志函数log(date, importance, message)经柯里化后传入单个参数log(date)或两个参数log(date, importance)时均会返回对应偏函数。函数柯里化详解Boale_H 原创已于 2022-08-08 14:20:04 修改本文系统阐述函数柯里化Currying的定义通过实例说明普通函数向柯里化函数的转换方法包含通用柯里化函数的实现逻辑。同时分析柯里化的应用场景例如参数复用、延迟执行等并结合bind方法说明其实现原理。最后对柯里化的优缺点进行归纳包括便于测试与复用、存在多层函数嵌套与效率较低等特征。函数柯里化的定义柯里化Currying也被称为部分求值。柯里化函数接收部分参数后不立即执行计算而是返回新函数已传入参数通过闭包保存。当函数执行实际计算时所有保存的参数被一次性用于运算。在数学定义中柯里化是一种函数转换操作可将调用形式为f ( a , b , c ) f(a, b, c)f(a,b,c)的函数转换为分步单参数调用形式f ( a ) ( b ) ( c ) f(a)(b)(c)f(a)(b)(c)。工程场景下的 JavaScript 柯里化实现通常为扩展形式同时支持f ( a , b ) ( c ) f(a, b)(c)f(a,b)(c)与f ( a ) ( b , c ) f(a)(b, c)f(a)(b,c)等多参数分批调用方式。固定部分参数并返回接收剩余参数的新函数该类函数也被称为部分计算函数用于缩小适用范围构建针对性更强的函数。该方法将多参数函数拆分为单参数或部分参数子函数内部依次返回并调用后续子函数完成剩余参数处理。柯里化不执行函数调用仅对函数结构进行转换。函数柯里化示例以日志打印功能为例对比普通函数与柯里化函数的实现差异。日志输出格式为日期 项目名 信息例如2022-07-29 xxx 后台管理系统 mm 接口异常该功能依赖date、project、message三个参数。普通函数实现// 参数date, project, messageconstlog(date,project,message){return${date}${project}${message}};constlogMsglog(2022-07-29,xxx 后台管理系统 ,mm 接口异常 );console.log(logMsg);// 输出 2022-07-29 xxx 后台管理系统 mm 接口异常柯里化函数实现日志场景中日期与项目名通常固定仅信息字段动态变化。通过柯里化保存固定参数避免重复传参。依据不同场景组合参数生成偏应用函数。constlog(date){return(projectName){return(message){return${date}${projectName}${message};};};};/* 日期、项目名、信息均不同的场景 */constlogMsg1log(2022-07-29)(A 项目 )( 接口报错 );console.log(logMsg1);constlogMsg2log(2022-08-01)(B 项目 )( 接口成功 );console.log(logMsg2);/* 日期相同、项目名与信息不同的场景 */constsameDateLoglog(2022-07-29);constlogMsg3sameDateLog(A 项目 )( 接口异常 );console.log(logMsg3);constlogMsg4sameDateLog(B 项目 )( 接口超时 );console.log(logMsg4);/* 日期与项目名相同、信息不同的场景 */constsameDateProjectNameLoglog(2022-07-29)(A 项目 );constlogMsg5sameDateProjectNameLog( 网络异常 );console.log(logMsg5);通用柯里化函数实现基于递归与闭包实现通用柯里化转换函数// 递归与闭包实现函数柯里化constcurryfunction(fn){constlenfn.length;// 获取原函数形参数量returnfunctiont(){constinnerLengtharguments.length;constargsArray.prototype.slice.call(arguments);if(innerLengthlen){// 递归终止条件returnfn.apply(undefined,args);}else{returnfunction(){constinnerArgsArray.prototype.slice.call(arguments);constallArgsargs.concat(innerArgs);returnt.apply(undefined,allArgs);};}};};// 测试用例functionadd(num1,num2,num3,num4,num5){returnnum1num2num3num4num5;}constfinalFuncurry(add);constresult1finalFun(1)(2)(3)(4)(5);constresult2finalFun(1,2)(3)(4)(5);constresult3finalFun(1,2,3)(4)(5);constresult4finalFun(1,2,3)(4,5);console.log(result1,result2,result3,result4);// 15 15 15 15说明apply递归调用中默认传入undefined实际场景可根据需求传入上下文对象context。工程开发中推荐使用 lodash.curry具体逻辑可参考 curry 源码。典型面试题实现add函数满足以下调用行为add(1)// 1add(1)(2)// 3add(1)(2)(3)// 6add(1)(2)(3)(4)// 10add(1)(2,3)// 6add(1,2)(3)// 6add(1,2,3)// 6函数柯里化的应用参数复用对重复使用的固定参数进行保存避免重复传参。延迟执行参数数量未满足原函数形参数量时返回新函数不立即执行计算。bind方法为该特性的典型应用。函数式编程基础作为组合compose、函子functor、单子monad等结构的实现基础。bind方法中的柯里化应用(function(){functionmyBind(contextwindow,...outerArgs){let_thisthis;returnfunction(...innerArgs){_this.call(context,...innerArgs.concat(outerArgs));};}Function.prototype.myBindmyBind;})();letobj{name:OBJ};document.body.onclickfn.myBind(obj,100,200);函数柯里化的优缺点优点原函数调用能力保持完整。支持快速构建各类偏函数。调用入口统一。便于单元测试与代码复用。缺点存在多层函数嵌套结构。闭包机制可能增加内存占用存在内存泄漏风险。递归实现会降低执行效率。arguments对象访问会降低变量读取速度。js 函数柯里化详解卖菜的小白 原创已于 2022-02-27 00:19:08 修改一、函数柯里化定义函数柯里化指传入部分参数后返回新函数由新函数接收剩余参数的转换方式。二、基础柯里化实现未柯里化实现示例柯里化转换示例简写形式三、函数柯里化的优势在函数处理流程中可通过柯里化实现单一职责拆分。例如对首个参数执行加2 22操作、第二个参数执行乘2 22操作、第三个参数执行平方操作后求和。四、自动柯里化实现functionmyCurried(fn){returnfunctioncurry(...args1){if(args1.lengthfn.length){returnfn.call(null,...args1);}else{returnfunction(...args2){returncurry.apply(null,[...args1,...args2]);};}};}functionsum(a,b,c,d,e){returnabcde;}letresFuncmyCurried(sum);console.log(resFunc(1,3,4)(1)(23));解析fn.length用于获取原函数形参数量。实现逻辑基于递归思想完成参数收集与调用。reference柯里化函数Currying什么是柯里化为什么要进行柯里化高级柯里化函数的实现-CSDN博客https://blog.csdn.net/m0_52409770/article/details/123359123函数柯里化详解-CSDN博客https://blog.csdn.net/Boale_H/article/details/126058783函数柯里化详解原理、实现与实战应用-CSDN博客https://blog.csdn.net/weixin_47450807/article/details/123152265javascript - 高级函数技巧-函数柯里化 - originJS - SegmentFault 思否https://segmentfault.com/a/1190000018265172

更多文章