3

What's the best way to indicate a function uses the 'arguments' object?
This is obviously opinion based but are there any conventions? When would it be better to use an array of arguments?

Some examples:

// Function takes n arguments and makes them pretty.
function manyArgs() {
    for (var i = 0; i < arguments.length; i++)
    console.log(arguments[i]);
}

function manyArgs( /* n1 , n2, ... */ )

function manyArgs(n1 /*, n2, ... */ )

function manyArgs(argArray)
Jonathan
  • 8,771
  • 4
  • 41
  • 78
  • 2
    Be sure you keep in mind the [performance implications](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers) of the `arguments` object. – Pointy Nov 02 '14 at 15:10
  • Personally I use `var_args` as recommended in the Google style guide/Closure compiler. If this actually provides a useful hint to someone looking at an uncommented function for the first time, who knows. – Alex K. Nov 02 '14 at 15:10
  • @Pointy thanks for the link. interesting stuff. – Jed Schneider Nov 02 '14 at 16:00
  • 1
    Just to clarify, your question is how to **indicate** (notationally, including comments) that a function has optional arguments? –  Nov 02 '14 at 16:23
  • @torazaburo Not necessarily optional, but yes, to indicate that the function is variadic. – Jonathan Nov 02 '14 at 16:36
  • @JaredFarrish I don't get it, you're implying it's bad form? – Jonathan Nov 02 '14 at 16:37
  • I think this is entirely a matter of personal preference. My own preference would be to place a comment in the argument list such as you have done. Putting `argArray` there seems somewhere between incorrect and misleading. –  Nov 02 '14 at 16:39
  • Are you using any documentation solution in your code? If not, consider it. If yes, there must be a proper way to document variadic functions. For example, for JSDoc it appears to be [`@param {...number} var_args`](http://stackoverflow.com/questions/4729516/correct-way-to-document-open-ended-argument-functions-in-jsdoc). – georg Nov 04 '14 at 13:19

3 Answers3

4

I never use variadic arguments in JavaScript. There are many better ways of structuring your code. For example, I would rewrite your code as follows:

[1,2,3,4,5].forEach(log); // instead of manyArgs(1,2,3,4,5);

function log(a) {
    console.log(a);
}

It's clear and concise.

Another example, if you want to find the sum of a list of numbers in JavaScript:

[1,2,3,4,5].reduce(add, 0); // instead of add(1,2,3,4,5);

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

There are so many useful abstractions available to structure your code that I don't see the benefit of using variadic arguments at all.

I do however use the arguments object for default values:

function foo(a,b,c) {
    switch (arguments.length) {
    case 0: a = "default argument for a";
    case 1: b = "default argument for b";
    case 2: c = "default argument for c";
    }

    // do something with a, b & c
}

Hence my advise to you would be to not use variadic arguments at all. Find a better abstraction for your code. I've never encountered the need to use variadic arguments in the 8 years that I have been programming in JavaScript.


Edit: I would advocate using a more functional approach to writing code. We can use currying to make code more succinct:

function curry(func, length, args) {
    switch (arguments.length) {
    case 1: length = func.length;
    case 2: args = [];
    }

    var slice = args.slice;

    return function () {
        var len = arguments.length;
        var a = args.concat(slice.call(arguments));
        if (len >= length) return func.apply(this, a);
        return curry(func, length - len, a);
    };
}

Using curry we can rewrite the sum example as follows:

var reduce = curry(function (func, acc, a) {
    var index = 0, length = a.length;
    while (index < length) acc = func(acc, a[index++]);
    return acc;
});

var sum = reduce(add, 0);

sum([1,2,3,4,5]); // instead of add(1,2,3,4,5);

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

Similarly for Math.max and Array.prototype.concat:

var reduce1 = curry(function (func, a) {
    if (a.length === 0)
        throw new Error("Reducing empty array.");
    return reduce(func, a[0], a.slice(1));
});

var maximum = reduce1(max);

maximum([1,2,3,4,5]); // instead of Math.max(1,2,3,4,5);

function max(a, b) {
    return a > b ? a : b;
}

var concat = reduce(function (a, b) {
    return a.concat(b);
}, []);

concat([[1,2],[3,4],[5,6]]) // instead of [1,2].concat([3,4],[5,6])

As for Array.prototype.push, because it mutates the input array instead of creating a new one, I prefer using array.concat([element]) instead of array.push(element):

var push = reduce(function (a, e) {
    return a.concat([e]);
});

push([1,2,3], [4,5]); // instead of [1,2,3].push(4, 5)

So what are the advantages of writing code this way:

  1. Currying is awesome. It allows you to create new functions from old ones.
  2. Instead of using variadic arguments you are passing arrays to functions. So you don't need any special way to indicate that the function uses arguments.
  3. Suppose you have to find the sum of an array named array. Using this method you just do sum(array). If you use variadic arguments then you would need to do add.apply(null, array).
  4. Suppose you want to find the sum of a, b & c. All you need to do is sum([a,b,c]), as compared to add(a, b, c). You need to put in the extra [] brackets. However doing so makes your code more understandable. According to the zen of python “explicit is better than implicit”.

So these are some of the reasons I never use variadic arguments in my programs.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • +! for the switch/case/fallthough pattern for optional arguments. –  Nov 02 '14 at 16:22
  • Nice trick with the switch when you can't use `a=a||"default"`. I have to say that I find `add(1,2,3,4,5)` far more obvious/readable than `[1,2,3,4,5].reduce(add, 0);` – Jonathan Nov 02 '14 at 16:52
  • 1
    I guess you should contact the designers of `Math.max`, `Array.push`, `Array.concat` etc. right away and tell them that their design was wrong. –  Nov 02 '14 at 16:53
  • @torazaburo I never said that using variadic arguments is wrong. I only said that there are better abstractions available that you wouldn't need to use variadic arguments. In practice I have never used `Math.max` on more than two numbers and I have never used `Array.prototype.push` on more than one element. I have used `Array.prototype.concat.apply([], [[],...])`, which is kind of like using `concat` in Haskell: `concat :: [[a]] -> [a]`. However they way I use it (using `apply`) I'm still only passing it two arguments (`[]` and `[[],...]`). – Aadit M Shah Nov 03 '14 at 01:49
  • 1
    @AaditMShah Thanks for your note. I know you said "I would never use", not "you should never use", and I basically agree with you, but it's also the case that API's are designed both for you and potentially for other consumers. In some cases, `sum(1,2,3)` could be considered a more friendly API than `sum([1,2,3])`, even though the latter is a bit easier to implement. In that case, as I understand the OP's question, he is asking how he should annotate/comment the `sum` definition to indicate that it is variadic. –  Nov 03 '14 at 02:38
  • @torazaburo I don't know how to annotate functions which use variadic arguments other than naming it `var_funcname` or something like that. However, that is by no means a convention. I don't think JavaScript has a convention for variadic arguments simply because not many people use variadic arguments. Hence everyone would have their own opinion about how to annotate these functions, which means that this question should be closed as being primarily opinion based. My answer addresses a more fundamental issue: that variadic arguments (in 99% of use cases) are not the best abstraction to use. =) – Aadit M Shah Nov 03 '14 at 02:51
  • @torazaburo I also updated my answer showing why I prefer using `sum([1,2,3])` over `sum(1,2,3)`. It's in the numbered list of advantages at the end of my answer. TLDR: I don't agree that `sum(1,2,3)` is friendlier than `sum([1,2,3])`. At first glance it may look cleaner. However, it shows its ugly side when you start writing code like `sum.apply(null, array)`. On the other hand `sum([1,2,3])` is explicit (which according to the zen of python is a good thing). Sure you have to type in two extra measly `[]` brackets. However, it is worth it because you can now do `sum(array)` directly. Cheers. – Aadit M Shah Nov 03 '14 at 02:58
  • @AaditMShah Point well taken. I suppose it falls into the "beating a dead horse" category to note that ES6's spread operator somehow supports or even encourages variadic functions. Instead of `sum.apply(null, array)` you could say `sum(...array)`. Whatever. Thanks for the conversation. –  Nov 03 '14 at 03:30
  • [blah].reduce(add); You don't need the ,0). – 1983 Nov 03 '14 at 03:53
  • @mintsauce `.reduce(add)` wouldn't work on arrays with zero length. In addition, it's just one more computation. Instead of `N` computations, it takes `N + 1` computations. Asymptotically they are equivalent. For the purpose of explanation however, I prefer writing `.reduce(add, 0)`. – Aadit M Shah Nov 03 '14 at 13:20
0
// Using ES6 syntax ...
var foo = function(/*...args*/){
    // shim Array.from, then
    var args = Array.from(arguments);
    // or
    var args = [].slice.call(arguments);
};
1983
  • 5,882
  • 2
  • 27
  • 39
  • If you're going to use ES6 syntax, then you can just say `var foo = function(...args)`. That also neatly solves the OP's issue of how to document the variadic nature of the function. –  Nov 03 '14 at 11:38
  • 1
    @torazaburo Unfortunately, the definition of a function is not an indication that the function uses variadic arguments unless the end user is reading the definition (which in all probability the end user is not). The function call should indicate that the function is variadic, not the function definition. You can't expect all the users to read your function definitions before they use them. Documentation would definitely help though; and aside from a special naming convention for variadic functions it seems to me that documentation is the only other viable option. The question's opinion based. – Aadit M Shah Nov 03 '14 at 13:30
  • @AaditMShah You're right basically, although I doubt if `add(1,2,3)` would confuse anyone. Anyway, voted to close as opinion-based. –  Nov 03 '14 at 13:36
  • I picked this answer as it seems the most future proof notation for the definition. Good point also from @AaditMShah: "call should indicate that the function is variadic, not the function definition" – Jonathan Nov 04 '14 at 21:36
0

The clearest way is to use the spread operator available in ES6, CoffeeScript (where they are called "splats" and the three dots come after the identifer), and TypeScript (where they are called "rest parameters").

// Function takes n arguments and makes them pretty.
function manyArgs(...args) {
    for (var i = 0; i < args.length; i++)
        console.log(args[i]);
}

If you're in an environment where you can use them, of course. It is both better self-documenting, and avoids having to muck around with arguments.