在 javascript 中对具有 n 个参数的函数进行柯里化

Currying in javascript for function with n parameters

如果f :: (a, b) -> c,我们可以定义curry(f)如下:

咖喱(f) :: ((a, b) -> c) -> a -> b -> c

const curry = f => a => b => f(a, b);
const sum = curry((num1, num2) => num1 + num2);
console.log(sum(2)(3)); //5

我们如何实现带有 n 个参数的函数的通用 curry 函数?

警告:我没有功能背景,所以我的术语可能有点不对。

如果 "curry" 你的意思是 "create a new function that will call the original with some arguments pre-filled," ES5 和更早版本的一般解决方案如下(见评论):

// Add a function to the function prototype
Object.defineProperty(Function.prototype, "curry", {
  value: function() {
    // Remember the original function
    var f = this;
    // Remember the curried arguments
    var args = Array.prototype.slice.call(arguments);
    // Return a new function that will do the work
    return function() {
      // The new function has been called: Call the original with
      // the curried arguments followed by any arguments received in
      // this call, passing along the current value of `this`
      return f.apply(this, args.concat(Array.prototype.slice.call(arguments)));
    };
  }
});

// Usage:
function foo(a, b, c) {
  console.log(a, b, c);
}
var f = foo.curry(1, 2);
f(3);

在ES2015+中,我们可以使用rest args代替arguments:

// REQUIRES ES2015+ support in your browser!

// Add a function to the function prototype
Object.defineProperty(Function.prototype, "curry", {
  value: function(...curriedArgs) {
    // Remember the original function
    let f = this;
    // Return a new function that will do the work
    return function(...args) {
      // The new function has been called: Call the original with
      // the curried arguments followed by any arguments received in
      // this call, passing along the current value of `this`
      return f.apply(this, curriedArgs.concat(args));
    };
  }
});

// Usage:
function foo(a, b, c) {
  console.log(a, b, c);
}
let f = foo.curry(1, 2);
f(3);

如果我没理解错的话,我认为这是使用 ES6 的方式:

const curry = f => {
  const nargs = f.length;
  const vargs = [];
  const curried = (...args) => vargs.push(...args) >= nargs
    ? f(...vargs.slice(0, nargs))
    : curried;

  return curried;
};

const fn2 = curry((a, b) => a + b);
const fn3 = curry((a, b, c) => a * (b + c));
const fn4 = curry((a, b, c, d) => Math.pow(a, b * (c + d)));

console.log(fn2(1)(2)); // 1 + 2
console.log(fn3(2)(3)(4)); // 2 * (3 + 4)
console.log(fn4(2)(1, 3)(4)); // 2 ^ (1 * (3 + 4))

如果你想在 ES5 中这样做,这里有一个稍微冗长的方法:

function curry (f) {
  var nargs = f.length;
  var vargs = [];

  return function curried () {
    return vargs.push.apply(vargs, arguments) >= nargs
      ? f.apply(undefined, vargs.slice(0, nargs))
      : curried;
  };
}

var fn2 = curry(function (a, b) {
  return a + b;
});
var fn3 = curry(function (a, b, c) {
  return a * (b + c);
});
var fn4 = curry(function (a, b, c, d) {
  return Math.pow(a, b * (c + d));
});

console.log(fn2(1)(2)); // 1 + 2
console.log(fn3(2)(3)(4)); // 2 * (3 + 4)
console.log(fn4(2)(1, 3)(4)); // 2 ^ (1 * (3 + 4))

ES6/2015

const curry = fn => function curried(cargs) {
  return cargs.length >= fn.length ? fn.apply(this, cargs) : (...args) => curried([...cargs, ...args])
}([]);

const arg2 = curry((a, b) => a + b);
const arg3 = curry((a, b, c) => a * (b + c));
const arg4 = curry((a, b, c, d) => Math.pow(a, b * (c + d)));

console.log(arg2(1)(2)); // 1 + 2
console.log(arg3(2)(3)(4)); // 2 * (3 + 4)
console.log(arg4(2)(1, 3)(4)); // 2 ^ (1 * (3 + 4))

var curry = function(fn) {
  var args = Array.prototype.slice.call(arguments);
  if (args.length - 1 >= fn.length) return fn.apply(this, args.slice(1));
  return function() {
    return curry.apply(this, args.concat.apply(args, arguments));
  };
};

var arg2 = curry(function(a, b) {
  return a + b;
});
var arg3 = curry(function(a, b, c) {
  return a * (b + c);
});
var arg4 = curry(function(a, b, c, d) {
  return Math.pow(a, b * (c + d));
});

console.log(arg2(1)(2)); // 1 + 2
console.log(arg3(2)(3)(4)); // 2 * (3 + 4)
console.log(arg4(2)(1, 3)(4)); // 2 ^ (1 * (3 + 4))

有一种简单的方法可以使用无限参数来柯里化您的 sum 函数。

const add = (a) => {
  const next = b => add(a + b);
  next.valueOf = () => a
  return next;
};

const one = add(1);
console.log(one.valueOf());

const two = one + 1;
console.log(two);

const four = two + two;
console.log(four)

const six = add(four)(two);
console.log(six.valueOf());

const eleven = six(4)(1);
console.log(eleven.valueOf());

每次您使用另一个参数调用柯里化函数时,此 add 函数将 运行。就像 const six = four + two; 的情况一样,它 returns 来自前两次调用的值,链条一直在继续。

请记住,为了获得 原始值 我们需要调用 .valueOf().

以下是受 Juan Sebastián Gaitán 的 解决方案启发的解决方案我刚刚将其扩展用于以下情况:

  • 添加(1, 2)(2, 3)(1, 2, 3, 4).valueOf();
  • 添加(1,2,3,4).valueOf();
  • 添加(1)(2)(3)(4)(5).valueOf();

const add = (a, ...rest) => {
    a += rest.reduce((total, val) => {
        return total + val;
    }, 0);
    const next = (...b) => add(a + b.reduce((total, val) => {
        return total + val;
    }, 0));
    next.valueOf = () => a;
    //console.log('a', a, '; next: ', next, '; rest: ', ...rest);
    return next;
};
console.log(add(1, 2)(2, 3)(1, 2, 3, 4).valueOf()); //18
console.log(add(1,2,3,4).valueOf()); //10
console.log(add(1)(2)(3)(4)(5).valueOf()); //15

由于输出是一个函数,你需要valueOf()。获得价值。 由于 .reduce() ,该函数看起来有点麻烦,但它非常易于阅读。

这是递归函数和柯里化函数的一个很好的例子。