柯里化的定义

引自维基百科

计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数)的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

例子

  1. function(a b) {
  2. return a + b;
  3. }
  4. // 正常执行
  5. add(1, 2) // 3
  6. // 假设有个 curry 的函数
  7. const addCurry = curry(add);
  8. // 柯里化后执行方式
  9. addCurry(1,2) // 3
  10. addCurry(1)(2) // 3

用途

好了我们已经知道了什么是柯里化了,但柯里化到底有什么用了?

例子

  1. // 一个简答的 ajax 请求
  2. function ajax(type, url, data) {
  3. const xhr = new XMLHttpRequest();
  4. xhr.open(type, url, true);
  5. xhr.send(data);
  6. }
  7. // 每次发送一个 ajax 请求都需要写上一系列的参数,显然这样的非常大麻烦。
  8. ajax("POST", "www.example.com", "name=cxx");
  9. ajax("GET", "www.example.com", "name=cxx");
  10. // 利用 curry
  11. const ajaxCurry = curry(ajax);
  12. // 以 POST 类型请求数据
  13. const post = ajaxCurry("POST");
  14. post("www.example.com", "name=cxx");
  15. // 以 POST 类型请求来自于 www.example.com 的数据
  16. const postFromTest = post("www.example.com");
  17. postFromTest("name=cxx");

现在好了,我们知道了 curry 的作用。

  • 参数复用。
  • 延迟计算/运行。

实现 curry 函数(第一版)

  1. const curry = function (fn) {
  2. const args = [].slice.call(arguments, 1);
  3. return function () {
  4. const newArgs = args.concat([].slice.call(arguments));
  5. return fn.apply(this, newArgs);
  6. };
  7. };
  1. const addCurry1 = curry(add, 1, 2);
  2. addCurry1(); // 3
  3. //或者
  4. const addCurry2 = curry(add, 1);
  5. addCurry2(2); // 3
  6. //或者
  7. const addCurry3 = curry(add);
  8. addCurry3(1, 2); // 3

已经有柯里化的感觉了,但还是没有达到要求。不过可以借助这个函数来实现真正的 curry 函数。

实现 curry 函数(第二版)

  1. // 第二版
  2. function sub_curry(fn) {
  3. const args = [].slice.call(arguments, 1);
  4. return function () {
  5. return fn.apply(this, args.concat([].slice.call(arguments)));
  6. };
  7. }
  8. function curry(fn, length) {
  9. length = length || fn.length;
  10. const slice = Array.prototype.slice;
  11. return function () {
  12. if (arguments.length < length) {
  13. const combined = [fn].concat(slice.call(arguments));
  14. return curry(sub_curry.apply(this, combined), length - arguments.length);
  15. } else {
  16. return fn.apply(this, arguments);
  17. }
  18. };
  19. }
  1. const fn = curry(function (a, b, c) {
  2. return [a, b, c];
  3. });
  4. // 以下输出都是 ['a', 'b', 'c']
  5. console.log(fn("a", "b", "c"));
  6. console.log(fn("a", "b")("c"));
  7. console.log(fn("a")("b")("c"));
  8. console.log(fn("a")("b", "c"));

这个代码有点难理解,举一个简单的例子方便理解。

  1. function sub_curry(fn) {
  2. return function () {
  3. return fn();
  4. };
  5. }
  6. function curry(fn, length) {
  7. length = length || 4;
  8. return function () {
  9. if (length > 1) {
  10. return curry(sub_curry(fn), --length);
  11. } else {
  12. return fn();
  13. }
  14. };
  15. }
  16. const fn0 = function () {
  17. console.log(1);
  18. };
  19. const fn1 = curry(fn0);
  20. console.log(fn1()()()()); // 1

先从理解 curry 函数开始。

当执行 fn1() 时,函数返回:

  1. // fn0 看作 A
  2. curry(sub_curry(A))
  3. // 相当于
  4. curry(function(){
  5. return A()
  6. })

当执行 fn1()() 时,函数返回:

  1. curry(sub_curry(function(){
  2. return A()
  3. }))
  4. // 相当于
  5. curry(function(){
  6. return (function(){
  7. return A()
  8. })()
  9. })
  10. // 相当于
  11. curry(function(){
  12. return A()
  13. })

当执行 fn1()()() 时,函数返回:

  1. // 跟 fn1()() 的分析过程一样
  2. curry(function(){
  3. return A()
  4. })

当执行 fn1()()()() 时,因为此时的 length > 2 为 false,所以执行 fn():

  1. fn()
  2. // 相当于
  3. (function(){
  4. return A()
  5. })()
  6. // 相当于
  7. A()
  8. // 执行 A(fn0) 函数,打印 1

更易懂的实现

  1. function curry(fn, args) {
  2. const length = fn.length;
  3. args = args || [];
  4. return function () {
  5. let _args = args.slice(0), arg, i;
  6. for (i = 0; i < arguments.length; i++) {
  7. arg = arguments[i];
  8. _args.push(arg);
  9. }
  10. if (_args.length < length) {
  11. return curry.call(this, fn, _args);
  12. } else {
  13. return fn.apply(this, _args);
  14. }
  15. };
  16. }
  17. const fn = curry(function (a, b, c) {
  18. console.log([a, b, c]);
  19. });
  20. fn("a", "b", "c"); // ["a", "b", "c"]
  21. fn("a", "b")("c"); // ["a", "b", "c"]
  22. fn("a")("b")("c"); // ["a", "b", "c"]
  23. fn("a")("b", "c"); // ["a", "b", "c"]

参考:

[1] JavaScript专题之函数柯里化
[2] 柯里化