15

I'm trying to understand how a function works that is run with two parentheses and two parameters. Like so:

add(10)(10); // returns 20

I know how to write one that takes two params like so:

function add(a, b) {
  return a + b;
}

add(10,10); // returns 20

How could I alter that function so it could be run with one set of parameters, or two, and produce the same result?

Any help is appreciated. Literally scratching my head over this.

Thanks in advance!

realph
  • 4,481
  • 13
  • 49
  • 104
  • *"How could I alter that function so it could be run with one set of parameters, or two, and produce the same result?"* Why would you *want* to? – T.J. Crowder Apr 23 '15 at 11:26
  • ^^ This. Please explain what you are trying to do, not how you think you should do it. – Reinstate Monica Cellio Apr 23 '15 at 11:27
  • Thanks @Abdul for finding the dupe – Bergi Apr 23 '15 at 11:28
  • @T.J.Crowder In some languages _it's the exact same thing_, for example - Haskell and F#. – Benjamin Gruenbaum Apr 23 '15 at 11:31
  • @BenjaminGruenbaum: Interesting. Yeah, I haven't got into the pure functional languages. – T.J. Crowder Apr 23 '15 at 11:31
  • @T.J.Crowder well, in a line - you can use partial application to get _context_, imagine you have a function that connects to a database and takes a connection string and a table, so calling `connect("connString", "tableName")` is useful, but you usually really want to do `let table = connect("connString");` and then do `table("tableName)` all around without having to type the connection string over and over :) – Benjamin Gruenbaum Apr 23 '15 at 11:36
  • near duplicate (subset, actually) of the poorly named http://stackoverflow.com/questions/29376702/tail-function-in-javascript – Touffy Apr 23 '15 at 11:38
  • @Touffy: I think that's a very different question. Certainly it has a very different answer. – Scott Sauyet Apr 23 '15 at 12:00
  • @ScottSauyet It's a generalization of this question to *n* arguments, and the answer it gives is totally applicable here. Yes, they're different, which is why I didn't flag as duplicate. But finding a special solution for *n* = 2 when there's a nice one for any *n* still seems like a waste of time. – Touffy Apr 23 '15 at 14:18
  • @Touffy: A generalization to _n_ arguments for a fixed _n_ would make sense, and my answer below answers that question as well. But the linked question is to my mind substantially different: it wants to call an unlimited number of arguments, and then either automatically (via `valueOf` in some answers) or by a call with no parameters (in others) manually end the chain of calls. It's not just the implementation which is different, but the fundamental API. – Scott Sauyet Apr 23 '15 at 14:58

4 Answers4

23

How could I alter that function so it could be run with one set of parameters, or two, and produce the same result?

You can almost do that, but I'm struggling to think of a good reason to.

Here's how: You detect how many arguments your function has received and, if it's received only one, you return a function instead of a number — and have that function add in the second number if it gets called:

function add(a,b) {
  if (arguments.length === 1) {
    return function(b2) { // You could call this arg `b` as well if you like,
      return a + b2;      // it would shadow (hide, supercede) the one above
    };
  }
  return a + b;
}
console.log(add(10, 10)); // 20
console.log(add(10)(10)); // 20

I said "almost" above because just because the add function received only one argument, that doesn't guarantee that the caller is going to call the result. They could write:

var x = add(10);

...and never call the function that x now refers to.

ibrahim mahrir
  • 31,174
  • 5
  • 48
  • 73
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • It could be really fun to generalize it to "autocurry" (that is, calling `autocurry`) on a function does this for N arguments and then showing really useful examples of [point-free programming](http://en.wikipedia.org/wiki/Tacit_programming) - pretty much similar to [this video](https://www.youtube.com/watch?v=m3svKOdZijA) – Benjamin Gruenbaum Apr 23 '15 at 11:34
  • If you're looking for good reasons to do this, check out the Lonsdorf video Benjamin mentions, or the post from Jackson linked in my answer. It can lead to some really readable code. – Scott Sauyet Apr 23 '15 at 11:53
  • 1
    I never knew you could detect the arguments being passed. Really helpful! Thanks for this. – realph Apr 23 '15 at 12:07
  • if i need 'catch' to manage errors in 'add(10)(10)', where to do it ? at the end i have error – stackdave Oct 18 '17 at 22:09
  • @stackdave: Not sure what you're asking. Just a `try`/`catch` around the call should do it. – T.J. Crowder Oct 19 '17 at 06:47
13

Welcome to the wonderful world of first order functions

In JavaScript, a function can return a function since a function is just another object. A simple implementation is something like:

function add(x){
    return function addOther(y){
        return x + y;
    };
}

This is possible because of closures and first order functions.

This also lets you do partial application, libraries like Ramda utilize this to great extent.

var addThree = add(3)
addThree(5); // 8
Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
4

To extend what both T. J. Crowder and Benjamin Gruenbaum said, libraries like Ramda (disclosure: I'm one of the authors) allow you to convert a simple function like this:

function add(a, b) {
    return a + b;
}

into the style under discussion by wrapping it in a call to a curry function:

var add = R.curry(function add(a, b) {
    return a + b;
});

add(3, 5); //=> 8
add(3)(5); //=> 8
var add3 = add(3);
add3(5); //=> 8

The best article I know on this subject is Hugh Jackson's Why Curry Helps. I wrote a more detailed one at Favoring Curry.


Update

Here is a version of curry somewhat simpler than the one in Ramda. It would do the above and quite a bit more, but doesn't do some of the things that Ramda does with placeholder values:

// here is a function that takes a function and returns a curried version
// of it, that is, a version that performs the sort of partial application
// you describe.
var curry = function(fn) {
    // first, we detect how many arguments the function has.
    var fnArity = fn.length; 
    var partialApply = function(args) { 
        // now, let's create a function that's curried
        return function () {
            // collect the previous args as the partial, and add the new 
            // ones you just received
            var newArgs = (args || []).concat([].slice.call(arguments, 0));
            // if we have "enough" arguments, we don't need any more partial
            // application and we can call the function.
            if (newArgs.length >= fnArity) {
                return fn.apply(this, newArgs);
            } else { // else we return a partially applied version
                return partialApply(newArgs);
            }
        };
    };

    return partialApply([]); // a function is itself partially applied with 0 args
};
Community
  • 1
  • 1
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • I was looking for a pure JS way, but I've given Curry a look and it looks very interesting nonetheless. I might use it on a future project. Thanks! – realph Apr 23 '15 at 12:08
  • @ScottSauyet thanks, that's much better :) I've added comments to the implementation, feel free to revert and/or change some of the changes I made - some of the comments are over pretty trivial code bits but I think all of it would benefit someone who has not done any currying before. – Benjamin Gruenbaum Apr 23 '15 at 13:18
  • Thank you @BenjaminGruenbaum for all the annotations! – Scott Sauyet Apr 23 '15 at 14:01
0
function add() {
  var sum = 0;
  for (var i = 0; i < arguments.length; i++) {
    sum += arguments[i];
  }
  function total() {
    for (var i = 0; i < arguments.length; i++) {
      sum += arguments[i];
    }
    return total;
  }
  total.toString = function () { return sum };

  return total;
}

This will work for any no of arguments and parentheses.

https://medium.com/@imdebasispanda/super-function-with-closure-86a58a9a980b

Debasis Panda
  • 433
  • 1
  • 6
  • 15