这一节的内容,主要针对javascript函数式编程的两个重要概念,偏函数(partial application) 和函数柯里化(curry)进行介绍。着重讲解underscore中对于偏函数应用的实现。

四, 偏函数和函数柯里化

4.1 基本概念理解

javascript的函数式编程有两个重要的概念,偏函数(partial application)和函数柯里化(curry)。理解这两个概念之前,我们需要先知晓什么是函数式编程? 函数式编程是一种编程风格,它可以将函数作为参数传递,并返回没有副作用的函数。而什么是偏函数应用(partial application), 通俗点理解,固定一个函数的一个或者多个参数,也就是将一个 n 元函数转换成一个 n - x 元函数;函数柯里化(curry)的理解,可以概括为将一个多参数函数转换成多个单参数函数,也就是将一个 n 元函数转换成 n 个一元函数。举两个简单的例子方便大家理解并对比其本质区别。

// 偏函数
function add(a, b, c) {return a + b + c
}
var resultAdd = partial(add, 1); // partial 为偏函数原理实现
resultAdd(2, 3) // 将多参数的函数转化为接收剩余参数(n-1)的函数// 函数柯里化
function add (a, b, c) {return a + b + c
}
var resultAdd = curry(add) //  curry 为柯里化实现
resultAdd(1)(2)(3)  // 将多参数的函数转化成接受单一参数的函数
复制代码

在underscore中只有对偏函数应用的实现,并没有函数柯里化的实现,因此本文只对underscore偏函数的实现做详细探讨,而柯里化实现只会在文末简单提及。(tips: lodash 有针对curry的函数实现)

4.2 rest参数

偏函数和柯里化的实现依赖于reset参数的概念,这是一个ES6的概念,rest参数(...rest)用于获取函数的多余参数,比如;

function add (a, ...values) { console.log(values) } // [2,4,6]
add(1, 2, 4, 6) //  获取除了第一个之后的剩余参数并以数组的形式返回。
复制代码

underscore中的restArguments方法,实现了与ES6中rest参数语法相似的功能,restArguments函数传递两个参数,function 和起始reset的位置,返回一个function的版本,该版本函数在调用时会接收来自起始rest位置后的所有参数,并收集到一个数组中。如果起始rest位置没有传递,则根据function本身的参数个数来确定。由于描述比较晦涩难懂,我们可以举一个具体的例子

var result = function (a, b, c) {console.log(a) // 3console.log(b) // 15console.log(c) // [2, 3, 2]return 'haha'
}
var raceResults = _.restArguments(result);
raceResults(3,15,2,3,2)
复制代码

result函数从接收三个参数,经过restArguments方法转换后,将接收的多余参数以数组的方式存储。当传递起始reset位置即startIndex时,实例如下:

var result = function (a, b, c) {console.log(a) // 3console.log(b) // [15, 2, 3, 2]console.log(c) // undefinedreturn ''
}
var raceResults = _.restArguments(result, 1);
raceResults(3,15,2,3,2)
复制代码

startIndex 会指定原函数在何处将余下的参数转换成rest,例子中会在第一个参数之后将参数转成rest数组形式。因此有了这两种情景,我们可以实现一个简化版的restArguments方法,具体的思路可以参考代码注释

/*** 模仿es6 reset参数* fn  函数* [startIndex]: 接收参数的起始位置,如未传递,则为fn本身参数个数*/
_.restArguments = function (fn, startIndex) {return function () {var l = startIndex == null ? fn.length - 1 : startIndex; // 如果没有传递startIndex,则rest数组的起始位置为参数倒数第二个l = l - fn.length < 0 ? l : 0; // 如果startIndex有传递值,但该值超过函数的参数个数,则默认将rest数组的起始位置设为第一个var arr = []var args = slice.call(arguments);for (var i = 0; i < l; i++) {arr.push(args[i]) // arr 存储startIndex前的参数}var restArgs = slice.call(arguments, l)arr.push(restArgs) // 将startIndex后的参数以数组的形式插入arr中,eg: arr = [1,3,4,[2,5,6]]return fn.apply(this, arr) //  调用时,fn参数参数形式已经转换成 1,3,4,[2,5,6]}
}
复制代码

restArgument实现rest参数的形式,本质上是改变参数的传递方式,函数调用时会将指定位置后的参数转化成数组形式的参数。

4.3 不绑定this指向的偏函数应用

在4.1的偏函数概念理解中,我们已经了解了偏函数的概念和使用形式,即将多参数的函数转化为接收剩余参数(n-1)的函数。在underscore中_.partial方法提供了对偏函数的实现。

// 使用
_.partial(function, *arguments)
// 举例
var subtract = function(a, b) { return b - a; };
sub5 = _.partial(subtract, 5);
sub5(20); // 15
// 可以传递_ 给arguments列表来指定一个不预先填充,但在调用时提供的参数
subFrom20 = _.partial(subtract, _, 5);
subFrom20(20); // -15
复制代码

有了restArguments的基础,实现一个partial函数便水到渠成。调用partial时,函数经过restArguments这层包装后,函数的剩余参数直接转成rest数组的形式,方便后续逻辑处理。

/*** 偏函数* 不指定执行上下文*/
_.partial = _.restArguments(function (fn, reset) { //  将后续参数转化成rest数组形式return function () {var position = 0var placeholder = _.partial.placeholder; //  占位符,预先不填充,调用时填充var length = reset.length;var args = Array(length);for (var i = 0; i < length; i++) {args[i] = reset[i] === placeholder ? arguments[position++] : reset[i]; // 预先存储partial封装时传递的参数,当遇到占位符时,用partial处理后函数调用传递的参数代替。}while (position < arguments.length) {args.push(arguments[position++]) // 将partial处理后函数调用的参数和原存储参数合并。真正调用函数时传递执行。}return fn.apply(this, args)}
})_.partial.placeholder = _;
复制代码

偏函数的思想,本质上可以这样理解,将参数保存起来,在调用函数时和调用传递参数合并,作为真正执行函数时的参数。

4.4 绑定this指向的偏函数应用

_.partial方法虽然实现了偏函数,但是当方法的调用需要结合上下文时,patial方法无法指定上下文,例如

var obj = {age: 1111,methods: function (name, time) {return name + '' + this.age + time }
}var sresult = _.partial(obj.methods, 3);
console.log(sresult(5)) // 3undefined5
复制代码

从偏函数的定义我们知道,原生javascript中,Function.prototype.bind()已经可以满足偏函数应用了

function add3(a, b, c) { return a+b+c; }
add3(2,4,8);  // 14var add6 = add3.bind(this, 2, 4);
add6(8);  // 14
复制代码

而在underscore同样封装了这样的方法,_.bind(function, object, *arguments) , 从bind函数的定义中可以知道,该方法将绑定函数 function 到对象 object 上, 也就是无论何时调用函数, 函数里的 this 都指向这个 object,并且可以填充函数所需要的参数。它是一个能结合上下文的偏函数应用,因此只需要修改partial的调用方式即可实现bind方法。

/*** bind* 偏函数指定this*/
_.bind = _.restArguments(function (fn, obj, reset) {return function () {var position = 0var placeholder = _.partial.placeholder;var length = reset.length;var args = Array(length);for (var i = 0; i < length; i++) {args[i] = reset[i] === placeholder ? arguments[position++] : reset[i]}while (position < arguments.length) {args.push(arguments[position++])}return fn.apply(obj, args) // 指定obj为执行上下文}
})
复制代码
4.5 其他版本偏函数

至此,underscore中关于偏函数的实现已经介绍完毕,其设计思想是先将参数保存起来,在调用函数时和调用传递参数合并,作为真正执行函数时的参数执行函数。因此抛离underscore,我们可以用arguments和es6的rest参数的方式来实现偏函数,下面提供两个简易版本。

// arguments版本
function partial(fn) {var args = [].slice.call(arguments, 1);return function() {return fn.apply(this, args.concat([].slice.call(arguments)))}
}
// es6 rest版本
function partial(fn, ...rest) {return (...args) => {return fn(...rest, ...args)   }
}
复制代码
4.6 函数柯里化

前文提到,underscore并没有关于函数柯里化的实现,只在它的相似库lodash才有对柯里化的实现。柯里化的思想是将一个多参数的函数拆分为接收单个参数的函数,接收单个参数的函数会返回另一个函数,直到接收完所有参数后才返回计算结果。因此,实现思路可以参考以下两种,es6版本和前者的实现思路相同。

// 完整版柯里化 ES3
function curry(fn) {if(fn.length < 2) return fn; // 当fn的参数只有一个或者更少时, 直接返回该函数并不需要柯里化。const generate = function(args, length) {return !length ? fn.apply(this, args) : function(arg) {return generate(args.concat(arg), length -1) // 循环递归调用,直到接收完所有参数(与函数参数个数一致), 将所有参数传递给fn进行调用。}}return generate([], fn.length)
}
// 完整版柯里化es6
function curryEs6(fn) {if(fn.length < 2) return fnconst generate = (args, length) => !length ? fn(...args) : arg => generate([...args, arg], length - 1);return generate([], fn.length)
}
复制代码

柯里化的实现思路多样,且衍生变种内容较多,这里不一一阐述,有时间再另写一篇深入探讨。而关于偏函数的应用,会有专门一节来介绍underscore中关于偏函数的应用,主要应用于延迟过程处理等。

  • 打造属于自己的underscore系列 ( 一 ) - 框架设计
  • 打造属于自己的underscore系列 ( 二 ) - 数据类型诊断
  • 打造属于自己的underscore系列 ( 三 ) - 迭代器(上)
  • 打造属于自己的underscore系列 ( 四 ) - 迭代器(下)
  • 打造属于自己的underscore系列(五)- 偏函数和函数柯里化
  • 打造属于自己的underscore系列(六)- 洗牌算法

打造属于自己的underscore系列(五)- 偏函数和函数柯里化相关推荐

  1. Python中的偏函数和函数柯里化

    偏函数(partial)和函数柯里化(currying)是函数式编程中常用的技术.有时候我们在复用已有函数时可能需要固定其中的部分参数,这除了可以通过默认值参数来实现之外,还可以使用偏函数.例如有个函 ...

  2. 深入理解javascript系列(十七):函数柯里化

    之前的系列,我们介绍了什么是高阶函数.所有以函数作为参数的函数,都可以叫作高阶函数.并且我们常常利用高阶函数来封装一些公共逻辑. 本次,我们要继续学习,继续记录,柯里化.柯里化,其实就是高阶函数的一种 ...

  3. 打造属于自己的underscore系列 ( 一 ) - 框架设计

    underscore作为开发中比较常用的一个javascript工具库,提供了一套丰富的函数式编程功能,该库并没有拓展原有的javascript原生对象,而是在自定义的_对象上,提供了100多个方法函 ...

  4. Javascript偏函数与柯里化

    Javascript偏函数与柯里化 到目前位置我们仅讨论绑定this,现在让我们更深入学习. 我们不仅能绑定this,也可以是参数,这较少使用,但有时很方便. bind完整的语法为: let boun ...

  5. Scala系列-4、scala中特质、柯里化、闭包等

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 传送门:大数据系列文章目录 目录 scala中特质 特质作为接口使用 特质中放置非抽象的成员 ...

  6. 打造属于自己的underscore系列 ( 一 )

    underscore作为开发中比较常用的一个javascript工具库,提供了一套丰富的函数式编程功能,该库并没有拓展原有的javascript原生对象,而是在自定义的_对象上,提供了100多个方法函 ...

  7. js系列二十三:函数柯理化

    函数柯理化其实就是高阶函数的应用 函数柯理化是指这样一个函数(假设叫做createCurry),他接受函数A作为参数,运行后能够继续返回一个新的函数,并且这个新的函数能够处理函数A的剩余参数. 这样定 ...

  8. python解复杂方程_Python数据处理篇之Sympy系列(五)---解方程

    前言 sympy不仅在符号运算方面强大,在解方程方面也是很强大. 本章节学习对应官网的:Solvers 官方教程 (一)求解多元一次方程-solve() 1.说明: 解多元一次方程可以使用solve( ...

  9. 《ASP.NET Core In Action》读书笔记系列五 ASP.NET Core 解决方案结构解析1

    <ASP.NET Core In Action>读书笔记系列五 ASP.NET Core 解决方案结构解析1 参考文章: (1)<ASP.NET Core In Action> ...

最新文章

  1. Go 源码里的这些 //go: 指令,go:linkname 你知道吗?
  2. 这是马?小鹏发布可骑乘「智能马」,四不像长相太奇葩!
  3. 使用VS2008怎么连接自带的SQL Server2005的Express版本数据库
  4. 【转】C++ vector的reserve和resize详解
  5. mybatis-plus与jpa在操作数据库时写法对比
  6. QML基础类型之real
  7. how to get context node reference CN0X from view controller reference
  8. .NET Core实践系列之SSO-跨域实现
  9. phpmyadmin支持php7,php 7-带有phpmyadmin的PHP 7给出了很多弃用声明
  10. 一、Rabbitmq安装与配置信息
  11. python unittest断言_python unittest之断言及示例
  12. 微信缓存dat怎么转图片_图片怎么转PDF?这个方法完美解决!
  13. fisco bcos 区块链配置文件位置
  14. 没错 企业想提升安全防护需要HR的合作
  15. DWORD winapi java_DWORD WINAPI?stdcall?
  16. ANSYS APDL学习(4):ANSYS 基本介绍
  17. 个人邮箱地址格式,如何能够正确的书写?
  18. php如何做left,php函数substr实现asp中left和right应用
  19. HTML怎么设置图片和文字间距离,div字间距-div内文字之间间距设置方法
  20. python数据处理系列之读写csv数据

热门文章

  1. AI一分钟 | 特朗普以国家安全为由否决博通收购高通;阿里发起时尚AI算法大赛
  2. 如何一夜暴富?这里有一份比特币价格预测指南
  3. 利用RNN训练Seq2Seq已经成为过去,CNN才是未来?
  4. 假期只是玩就没意思了!告诉你一个学习麻省理工大学所有课程的好地方
  5. 图片提取文字功能很神奇?Java几行代码搞定它!
  6. 单点登录系统用 8 张漫画就解释了
  7. 彻底搞懂 Nginx 的五大应用场景
  8. 微信8.0内测更新!!!(附内测体验资格)
  9. HTTP/2做错了什么?刚刚辉煌2年就要被弃用了!?
  10. 碰到Maven依赖冲突,想砸电脑?这个IDEA插件必须了解一下...