函数柯里化

栏目: 编程语言 · 发布时间: 5年前

内容简介:函数式编程是一种将函数作为参数传递和返回,并且没有副作用的一种编程方式。JavaScript,Haskell,Clojure,Scala 和 Erlang 是部分实现了函数式编程的语言。函数式编程也带来了很多概念函数柯里化(currying)又称部分求值。一个 currying 的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。Currying is

函数式编程是一种将函数作为参数传递和返回,并且没有副作用的一种编程方式。JavaScript,Haskell,Clojure,Scala 和 Erlang 是部分实现了函数式编程的语言。函数式编程也带来了很多概念

  • 纯函数(Pure Functions): 只是返回新的值,不改变系统变量
  • 柯里化(Currying): 接受多个参数的函数转换成接受单一参数的函数的操作
  • 高阶函数(Higher-order functions): 一个可以接收函数作为参数,甚至返回一个函数的函数 不过我们今天只是来了解柯里化函数是如何使用的

什么是函数柯里化

函数柯里化(currying)又称部分求值。一个 currying 的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

Currying is the process of turning a function with multiple arity into a function with less arity(柯里化是将一个多元函数转换为低元函数的操作)—Kristina Brainwave

举一个简单的例子

function adds(a, b, c) {
			return a + b + c
		}

		function currying(a) {
			return function (b) {
				return function(c){
					return a + b + c
				}
			}
		}

		adds(2, 3, 4)  // 9
		currying(2)(3)(4) // 9
复制代码

优化currying函数

上面currying函数可以实现较为简单的功能,结构也比较清晰,但是针对于更多参数的传递,我们不能无限的嵌套下去,或者针对于不同的传参方式,例如

currying(2, 3)(4)
		currying(2, 3, 4)
		currying(2)(3, 4)
复制代码

上面的函数就不能支持了,因此需要对currying函数进行下一步优化。

function adds(a, b, c) {
			return a + b + c
		}
		function currying(fn, length){
			length = length || fn.length;  // 第一次调用获取函数 fn 参数的长度,后续调用获取 fn 剩余参数的长度
			return function(){
				var args = [].slice.call(arguments)  // currying返回函数接受的参数
				if (args.length < length) {   // 判断参数的长度是否小于 fn 剩余参数需要接收的长度
					return curry(fn.bind(this, ...args), length - args.length)  // 递归 currying 函数,新的 fn 为 bind 返回的新函数(bind 绑定了 ...args 参数,未执行),新的 length 为 fn 剩余参数的长度
				}else {
					return fn.call(this, ...args)   // 执行 fn 函数,传入新函数的参数
				}
			}
		}

		var addCurry = currying(adds);
		addCurry(2)(3)(4) // 9
		addCurry(2, 3)(4)  // 9
		addCurry(2, 3, 4)  // 9
		addCurry(2)(3, 4) // 9
复制代码

我们通过判断是否传入足够数量的参数长度决定是执行函数还是递归currying返回新的函数。其中用到了call和bind。如果不想用call\apply\bind的功能。还有大神提供了最简版本

const currying = fn =>
			judge = (...args) =>
				args.length >= fn.length
					? fn(...args)
					: (...arg) => judge(...args, ...arg)

		var addCurry = currying(adds);
		addCurry(2)(3)(4) // 9
		addCurry(2, 3)(4)  // 9
		addCurry(2, 3, 4)  // 9
		addCurry(2)(3, 4) // 9
复制代码

上述两个currying函数的实现原理都是「用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数」。

实际应用场景

减少重复传递不变的部分参数

function volume(l, w, h) {
			return l * w * h
		}

		let volumeA = volume(100, 100, 50)
		let volumeB = volume(100, 100, 90)
复制代码

在计算货物体积时我们通常需要参入三个参数来计算体积,但是计算同样长和宽而不同高的货物,长度和宽度没有必要每次都输入。这时候我们的柯里化就可以运用于此,这样就减少重复传递不变的部分参数

var getvolume = currying(volume)(100, 100)
volumeA(50)
volumeB(60)
复制代码

动态创建函数

有一种典型的应用情景是这样的,每次调用函数都需要进行一次判断,但其实第一次判断计算之后,后续调用并不需要再次判断,这种情况下就非常适合使用柯里化方案来处理。即第一次判断之后,动态创建一个新函数用于处理后续传入的参数,并返回这个新函数。

下面的例子中,在 DOM 中添加事件时需要兼容现代浏览器和 IE 浏览器(IE < 9),方法就是对浏览器环境进行判断,看浏览器是否支持。一般情况下每次添加事件都会调用做一次判断。

function addEvent (type, el, fn, capture = false) {
    if (window.addEventListener) {
        el.addEventListener(type, fn, capture);
    }
    else if(window.attachEvent){
        el.attachEvent('on' + type, fn);
    }
}
复制代码

但是我们可以以利用闭包和立即调用函数表达式(IIFE)来使判断只用执行一次,动态创建新的函数用于处理后续传入的参数,这样做的好处就是之后调用就不需要再次计算了

const addEvent = (function(){
    if (window.addEventListener) {
        return function (type, el, fn, capture) {
            el.addEventListener(type, fn, capture);
        }
    }
    else if(window.attachEvent){
        return function (type, el, fn) {
            el.attachEvent('on' + type, fn);
        }
    }
})();
复制代码

bind的模拟实现

bind 的模拟实现本身就是一种柯里化,bind是用来改变函数执行时候的上下文,但是函数本身并不执行,所以本质上是延迟计算

var o = { color: "blue" };

function sayColor(){

    alert(this.color);

}

var objectSayColor = sayColor.bind(o);

objectSayColor();    //blue
复制代码

扩展

通过一个函数实现下列功能

add(1) // 1
		add(1)(2);  // 3
		add(1)(2)(3)// 6
		add(1)(2)(3)(4) // 10 
复制代码

初级版

function add(a) {
			function sum(b) { // 使用闭包
				a = a + b; // 累加
				return sum;
			}
			sum.toString = function () { // 重写toString()方法
				return a;
			}
			return sum; // 返回一个函数
		}
复制代码

进阶版本

function add() {
			let data = [].concat(Array.prototype.slice.call(arguments))
			function tmp() { // 使用闭包
			    data = data.concat(Array.prototype.slice.call(arguments))
				return tmp;
			}
			tmp.valueOf = function () {
				return data.reduce(((source, item) => source + item), 0);
			}
			tmp.toString = function () {
				return data.reduce(((source, item) => source + item), 0);
			}
			return tmp
		}
		add(1) // 1
		add(1,4)(2);  // 7
		add(1)(2, 5)(3)// 11
		add(1,5,6)(2,5)(3)(4, 4) // 30

复制代码

性能

  • 存取 arguments 对象通常要比存取命名参数要慢一些。
  • 一些老版本的浏览器在 arguments.length 的实现上相当慢。
  • 使用 fn.apply() 和 fn.call() 要比直接调用 fn() 要慢点。
  • 创建大量嵌套作用域和闭包会带来开销,无论是内容还是速度上。
  • 大多数瓶颈来自 DOM 操作

参考链接


以上所述就是小编给大家介绍的《函数柯里化》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python语言程序设计

Python语言程序设计

(美)Y. Daniel Liang / 机械工业出版社 / 2013-3 / 79.00元

本书保持了Liang博士系列丛书中一贯的、标志性的教与学的哲学:以实例教,由实践学。书中采用了他所提出的已经经过实践检验的“基础先行”的方法,即在定义类之前,首先使用清晰简明的语言介绍基本程序设计概念,如选择语句、循环和函数;在介绍面向对象程序设计和GUI编程之前,首先介绍基本逻辑和程序设计概念。书中除了给出一些以游戏和数学为主的典型实例外,还在每章的开始使用简单的图形给出一两个例子,以激发学生的......一起来看看 《Python语言程序设计》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

html转js在线工具
html转js在线工具

html转js在线工具