0

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
  • Do you mean f :: ((a, b) -> c) -> ( a-> b-> c)? Commented Jul 28, 2016 at 14:01
  • @Patrick Roberts yes Commented Jul 28, 2016 at 14:14
  • 1
    I love these currying challenges. I should write a library or something with a bunch of currying utilities, maybe people would find it worth using. Commented Jul 28, 2016 at 14:21
  • 1
    I have accepted and upvoted. Commented Jul 28, 2016 at 17:23
  • Where is the option to accept? I have clicked on up arrow on left side of your answer. Commented Jul 28, 2016 at 17:26

5 Answers 5

6

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

Sign up to request clarification or add additional context in comments.

4 Comments

@PatrickRoberts: Gah, I was looking here, and misread "enumerable" as "configurable". Excellent, that makes things a lot simpler.
@PatrickRoberts: Yeah, saw that, cool stuff! (I'd upvote again if I could.) But wouldn't it be better to keep length non-writable, and just bake in the right value for the generated function?
@T.J.Crowder You mentioned a couple methods but I don't think a Function constructor (which I considered the most viable) would be preferable to configuring the .length property. I'm pretty sure it was made configurable in the first place to allow niche scenarios like this to change it when it makes sense to.
@PatrickRoberts: Oh, absolutely you don't want the Function constructor. I was thinking more that you'd do Object.defineProperty(curry, "length" { value: appropriateLengthHere }) after generating the curried function. Just keeping it non-writable, but still using the fact it was left configurable (actually, add configurable: true to my example there -- may as well pay forward).
2

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

6 Comments

Apologies, was missing a ), didn't notice until I added the snippets.
I think we interpreted the question different ways, I guess we'll see whose interpretation was correct :P
@PatrickRoberts: Indeed! :-) They're both good answers to the question they're answering, we'll see which answers the OP's question.
@T.J. Crowder Thank you for the comments.
Just to make sure I understand, if you were to do let f = foo.curry(); f(1)(2)(3); would that do the same thing?
|
0

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

Comments

0

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

Comments

-1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.