在计算机科学中,柯里化(英语:Currying),又译为卡瑞化
或加里化
,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。这个技术由克里斯托弗·斯特雷奇
以逻辑学家哈斯凯尔·加里
命名的,尽管它是Moses Schönfinkel
和戈特洛布·弗雷格
发明的。
高阶函数的特点:
- 函数可以作为参数被传递;
- 函数可以作为返回值输出。
1 2 3 4 5 6 7
| var foo = function(a) { return function (b) { return function (c) { return a+b+c; }; }; };
|
反柯里化:函数柯里化的对偶是Uncurrying,一种使用匿名单参数函数来实现多参数函数的方法。
理解
Currying概念其实很简单,只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
简单求和函数
a+b+c
1 2 3 4 5 6 7
| function add(a, b, c) { return a + b + c; } console.log(add(1, 2, 3)) => 6
|
a+b+c Currying
1 2 3 4 5 6 7 8 9 10 11
| function add(x) { return function (y) { return function (z) { return x + y + z; } } } var addOne = add(1); var addOneAndTwo = addOne(2); var addOneAndTwoAndThree = addOneAndTwo(3); console.log(addOneAndTwoAndThree);
|
这里我们定义了一个add函数,它接受一个参数并返回一个新的函数。调用add之后,返回的函数就通过闭包的方式记住了add的第一个参数。一次性地调用它实在是有点繁琐,好在我们可以使用一个特殊的curry帮助函数(helper function)使这类函数的定义和调用更加容易。
用ES6的箭头函数,我们可以将上面的add实现成这样:
1
| const add = x => y => z => x + y + z;
|
好像使用箭头函数更清晰了许多。
1 2 3 4 5 6 7 8 9 10
| function addFn(x) { return function fn(y) { console.log(y) return fn } } console.log(addFn(1)(2)(3)(4)) => addFn(1) fn(2) fn(3) fn(4)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function Arguments() { console.log(arguments) var fn = function () { console.log(arguments) return fn } fn.toString = function () { return arguments; } return fn; } console.log(Arguments(1)(2)(3)(4)) function add(x) { return function (y) { return function (z) { return function (d) { return x + y + z + d; } } } } console.log(add(1)(2)(3)(4))
|
场景使用
实际场景的运用能更好的加深我们的理解,所以我们用实际的生活场景来看看 柯里化 如何运用。
获取数据
- 默认参数
- 剩余参数(Array)
- arguments对象(非Array)
数组拼接
arguments转数组
1 2
| Array.from(arguments) Array.prototype.slice.call(arguments)
|
剩余参数
1
| [...[1,3],...[1,2]] => [1, 3, 1, 2]
|
concat()
连接两个或更多的数组,并返回结果。
1 2 3 4 5
| var new_array = old_array.concat(value1[, value2[, ...[, valueN]]]) sum.concat(Array.from(arguments)) a.concat(4,[1, 2]) => [4, 1, 2] [].concat(4, 5) => [4, 5]
|
slice
方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象。原始数组不会被修改。
1 2
| [].slice.call([1, 2, 4], 1) => [2, 4] [].slice.call([1, 2]) => [1, 2]
|
1 2 3 4 5 6 7 8 9
| Array.slice() var animals = ['ant', 'bison', 'camel', 'duck', 'elephant']; console.log(animals.slice(2)); console.log(animals.slice(2, 4)); console.log(animals.slice(1, 5));
|
除了使用 Array.prototype.slice.call(arguments),你也可以简单的使用 [].slice.call(arguments) 来代替。另外,你可以使用 bind 来简化该过程。
1 2 3 4 5 6 7
| var unboundSlice = Array.prototype.slice; var slice = Function.prototype.call.bind(unboundSlice); function list() { return slice(arguments); } var list1 = list(1, 2, 3);
|
push
向数组的末尾添加一个或更多元素,并返回新的长度。
1 2 3 4 5 6 7
| [].push.apply(_args, [].slice.call(arguments)); Array.prototype.push.apply(_args, [].slice.call(arguments)) var a = [1]; var c = [1, 3, 2]; [].push.apply(a, c); console.log(a)
|
.apply()
a+b+c
x + y + z + d
1 2 3 4 5 6 7 8 9 10
| function add(x) { return function (y) { return function (z) { return function (d) { return x + y + z + d; } } } } console.log(add(1)(2)(3)(4))
|
求和
这段代码实际上还是不能满足要求的,题主测试成功也只是因为所在环境的 console.log 会将结果转为 string 输出,为了 return tmp 不至于输出一个function,所以重新定义了函数 tmp 的 toString 方法,使其返回 sum 值。
比如如果使用以下代码测试会发现问题
1 2 3 4 5 6 7 8 9 10 11
| function add(a) { function fn(b) { a = a + b; return fn; } fn.toString = function () { return a; } return fn; } console.log(add(1)(2)(3)(4));
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| function addCurry(x) { var sum = x; var fn = function (y) { sum = sum + y; return fn; }; fn.valueOf = function () { return sum; }; console.log('addCurry', fn); return fn; } console.log(addCurry(1)(2)(3)(4))
|
1 2 3 4 5 6 7 8 9 10 11 12
| function add() { var sum = arguments[0]; var fn = function () { sum = sum + arguments[0]; return arguments.callee; } fn.valueOf = function () { return sum; } return fn; } console.log(add(1)(2)(3)(4));
|
1 2 3 4 5 6 7 8 9 10
| var add = function(x, y) { if (arguments.length == 1) { return function(z) { return x + z; } } else { return x + y; } } console.log(add(2,5)(2)(5));
|
剩余参数
1 2 3 4 5 6 7 8 9 10 11
| function addReduce(...a){ let b = function(...b){ console.log(...[...a,...b]) return addReduce(...[...a,...b]) } b.valueOf = () => { return a.reduce((x,y) => x+y ) } return b; } console.log(addReduce(1)(2)(3)(4));
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function add(...a){ let sum = Array.from(a); let fn = function(...b) { sum = sum.concat(Array.from(b)); return fn; } fn.valueOf = () => { return sum.reduce((x, y) => { return x + y; }) } return fn; } console.log(add(1,11)(2)(2,3,4)(4));
|
arguments
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function add(){ var sum = Array.from(arguments); var fn = function() { let argsC = sum.concat(Array.from(arguments)); return add.apply(null, argsC) } fn.valueOf = function() { return sum.reduce((x,y) => { return x + y; }) } return fn; } console.log(add(1)(2)(3)(4));
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function add(){ let sum = Array.from(arguments); let fn = function() { sum = sum.concat(Array.from(arguments)); return arguments.callee; } fn.valueOf = () => { return sum.reduce((x, y) => { return x + y; }) } return fn; } console.log(add(1)(2)(3)(4));
|
自定义方法(偏函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| var currying = function (fn) { let args = []; let cuy = function() { [].push.apply(args, arguments); return arguments.callee; } cuy.valueOf = function() { return fn.apply(null, args); } return cuy } var add = currying(function() { let num = Array.from(arguments) return num.reduce((x, y) => { return x + y }) }) var ride = currying(function() { let num = Array.from(arguments) return num.reduce((x, y) => { return x * y }) }) console.log(add(1)(2)) console.log(add(1, 2)) console.log(add()) console.log(ride(1, 2, 5)) console.log(ride())
|
编写一个通用的 curry()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function curry(fn) { return function curried() { var args = [].slice.call(arguments); return args.length >= fn.length ? fn.apply(null, args) : function () { var rest = [].slice.call(arguments); return curried.apply(null, args.concat(rest)); }; }; } function foo(a,b,c) { return a+b+c; } var curriedFoo = curry(foo); console.log(curriedFoo(1,2,3)); console.log(curriedFoo(1)(2,3)); console.log(curriedFoo(1)(2)(3)); console.log(curriedFoo(1,2)(3));
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| var currying = function (fn) { let args = []; let cuy = function() { [].push.apply(args, arguments); return arguments.callee; } cuy.valueOf = function() { return fn.apply(null, args); } return cuy } var add = currying(function() { return args.reduce((x, y) => { return x + y }) }) var ride = currying(function() { return args.reduce((x, y) => { return x * y }) }) console.log(add(1)(2)) console.log(add(1, 2)) console.log(add()) console.log(ride(1, 2, 5)) console.log(ride())
|
记账本
记账本,每天记录使用多少钱,一个月算一次总花费(或者在我想要知道的时候)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function account(){ var total = []; function money(p) { if (arguments.length === 0) { total = total.reduce((sum, value) => { return sum + value; }, 0); console.log('一共花了', total + ' 元'); } else { total.push(p); console.log('今天花了', p+' 元'); } } return money; } var spend = account(); console.log(spend(15)); console.log(spend(30)); console.log(spend()); => 今天花了 15 元 今天花了 30 元 一共花了 45 元
|
老婆的测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| var currying = function(fn) { console.log(arguments); var args = [].slice.call(arguments, 1); console.log(args); return function() { console.log(arguments); var newArgs = args.concat([].slice.call(arguments)); return fn.apply(null, newArgs); }; }; var getWife = currying(function() { console.log(arguments); var allWife = [].slice.call(arguments); console.log(allWife.join(";")); }, "合法老婆"); console.log('大老婆') getWife("大老婆","小老婆","俏老婆","刁蛮老婆","乖老婆","送上门老婆"); console.log('超越韦小宝的老婆') getWife("超越韦小宝的老婆"); => Arguments(2) [ƒ, "合法老婆", callee: ƒ, Symbol(Symbol.iterator): ƒ] ["合法老婆"] 大老婆 Arguments(6) ["大老婆", "小老婆", "俏老婆", "刁蛮老婆", "乖老婆", "送上门老婆", callee: ƒ, Symbol(Symbol.iterator): ƒ] Arguments(7) ["合法老婆", "大老婆", "小老婆", "俏老婆", "刁蛮老婆", "乖老婆", "送上门老婆", callee: ƒ, Symbol(Symbol.iterator): ƒ] 合法老婆;大老婆;小老婆;俏老婆;刁蛮老婆;乖老婆;送上门老婆 超越韦小宝的老婆 Arguments ["超越韦小宝的老婆", callee: ƒ, Symbol(Symbol.iterator): ƒ] Arguments(2) ["合法老婆", "超越韦小宝的老婆", callee: ƒ, Symbol(Symbol.iterator): ƒ] 合法老婆;超越韦小宝的老婆
|
实现柯里化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| var currying = function(fn) { var args = []; return function() { if (arguments.length === 0) { return fn.apply(this, args); } else { [].push.apply(args, arguments); return arguments.callee; } } } var cost = (function() { var money = 0; return function() { for (var i = 0; i < arguments.length; i++) { money += arguments[i]; } return money; } })(); var costs = currying(cost); console.log(costs()); console.log(costs(100)); console.log(costs(200)(300)); console.log(costs(300)); console.log(costs());
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| var currying = function(fn) { var args = Array.prototype.slice.call(arguments, 1); return function() { if (arguments.length === 0) { return fn.apply(this, args); } else { [].push.apply(args, arguments); return arguments.callee; } } } var cost = (function() { var money = 0; return function() { for (var i = 0; i < arguments.length; i++) { money += arguments[i]; } return money; } })(); function uncurrying(fn) { return function(...args) { var ret = fn; for (let i = 0; i < args.length; i++) { ret = ret(args[i]); } return ret; }; } var curryingCost = currying(cost); var uncurryingCost = uncurrying(curryingCost); console.log(uncurryingCost(100, 200, 300)); console.log(uncurryingCost()());
|
f([‘1’,’2’]) => ‘12’
1 2 3 4 5 6 7 8 9
| var concatArray = function(chars) { return chars.reduce(function(a, b) { return a.concat(b); }); } concat(['1','2','3']) => '123'
|
固定易变因素
柯里化特性决定了它这应用场景。提前把易变因素,传参固定下来,生成一个更明确的应用函数。最典型的代表应用,是bind函数用以固定this这个易变对象。
1 2 3 4 5 6 7
| Function.prototype.bind = function(context) { var _this = this, _args = Array.prototype.slice.call(arguments, 1); return function() { return _this.apply(context, _args.concat(Array.prototype.slice.call(arguments))) } }
|
Uncurrying
函数柯里化的对偶是Uncurrying,一种使用匿名单参数函数来实现多参数函数的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| 可能遇到这种情况:拿到一个柯里化后的函数,却想要它柯里化之前的版本,这本质上就是想将类似f(1)(2)(3)的函数变回类似g(1,2,3)的函数。 下面是简单的uncurrying的实现方式: function uncurrying(fn) { return function(...args) { var ret = fn; for (let i = 0; i < args.length; i++) { ret = ret(args[i]); } return ret; }; } 注意,不要以为uncurrying后的函数和currying之前的函数一模一样,它们只是行为类似! var currying = function(fn) { var args = Array.prototype.slice.call(arguments, 1); return function() { if (arguments.length === 0) { return fn.apply(this, args); } else { [].push.apply(args, arguments); return arguments.callee; } } } function uncurrying(fn) { return function(...args) { var ret = fn; for (let i = 0; i < args.length; i++) { ret = ret(args[i]); } return ret; }; } var cost = (function() { var money = 0; return function() { for (var i = 0; i < arguments.length; i++) { money += arguments[i]; } return money; } })(); var curryingCost = currying(cost); var uncurryingCost = uncurrying(curryingCost); console.log(uncurryingCost(100, 200, 300)());
|
偏函数
Partial Application(偏函数应用) 是指使用一个函数并将其应用一个或多个参数,但不是全部参数,在这个过程中创建一个新函数。
1 2 3 4 5 6 7
| function add3(a, b, c) { return a+b+c; } add3(2,4,8); var add6 = add3.bind(this, 2, 4); add6(8);
|
var toString=object.prototype.toString;
var isString=function(obj){
return toString.call(obj)==’[object String]’;
};
var isFunction=function(obj){
return toString.call(obj)==’[object Function]’;
};
……….
偏函数方法:
var isType=function(type){
return function(obj){
return tostring.call(obj)==’[object ‘ + type+ ‘ ]’;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| var isType = function(type) { return function(obj) { return toString.call(obj) == '[object ' + type + ']'; } }; var isArray = isType('Array'); var isBoolean = isType('Boolean'); var isFunction = isType('Function'); var isNumber = isType('Number'); var isArray_ = isArray(['a','d']); var isBoolean_ = isBoolean(true); var isFunction_ = isFunction(isType); var isNumber_ = isNumber(888); alert("['a','d'] is an Array?" + isArray_); alert("true is a Boolean? " + isBoolean_); alert('isType is a Function?' + isFunction_); alert('888 is a Number?' + isNumber_);
|
拓展
-JavaScript函数柯里化
-我理解的函数柯里化
-高阶函数应用–currying
-从一道面试题谈谈函数柯里化 (Currying)
-Javascript中有趣的反柯里化技术
-为什么要柯里化(why-curry-helps)slide
-前端开发者进阶之函数柯里化Currying
-JS中的柯里化(currying)
-何为Curry化/柯里化?
-一道题看透函数柯里化(currying)
-掌握 JavaScript 函数的柯里化
-
-
-
-
-
ES6 中 curry 和 apply 的实现
apply() 在 ES6 中的实现:
1 2 3 4 5 6 7 8 9
| function curry(fn) { return function curried(...args) { return args.length >= fn.length ? fn.call(this, ...args) : (...rest) => { return curried.call(this, ...args, ...rest); }; }; }
|
apply() 在 ES6 中的实现:
1 2 3 4 5 6
| function apply(fn, ...args) { return (..._args) => { return fn(...args, ..._args); }; }
|