Currying in javascript for function with n paramet

2019-01-29 13:22发布

If f :: (a, b) -> c, we can define curry(f) as below:

curry(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

How do we implement generic curry function that takes a function with n parameters?

5条回答
该账号已被封号
2楼-- · 2019-01-29 14:09

Caveat: I don't have a functional background, so my terminology may be a bit off.

If by "curry" you mean "create a new function that will call the original with some arguments pre-filled," the general solution in ES5 and earlier is as follows (see comments):

// 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);

In ES2015+, we can use rest args instead of 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);

查看更多
冷血范
3楼-- · 2019-01-29 14:10

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))

ES5

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))

查看更多
闹够了就滚
4楼-- · 2019-01-29 14:11

If I understand correctly, I think this is the way to go using 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))

If you want to do this in ES5, here's a slightly more verbose method:

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))

查看更多
够拽才男人
5楼-- · 2019-01-29 14:11

Below is a solution inspired by Juan Sebastián Gaitán's solution I have just extended it for below cases:

  • add(1, 2)(2, 3)(1, 2, 3, 4).valueOf();
  • add(1,2,3,4).valueOf();
  • add(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

As the output is a function, you need valueOf(). to get the value. the function looks little cumbersome because of .reduce() but its very simple to read.

This is a good example of recursive function and a currying function.

查看更多
祖国的老花朵
6楼-- · 2019-01-29 14:24

There's a simple way to curry your sum function with unlimited parameters.

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());

This add function would run every time you call the curried function with another parameter. Like in the case for const six = four + two;It returns the value from two previous calls and the chain goes on and on.

Keep in mind that in order to get the primitive value we need to call .valueOf().

查看更多
登录 后发表回答