8

How can I have a function accept either named arguments (foo({a: 'hello', b: 'it is me'})) or positional arguments (foo('hello', 'it is me'))?

I understand that named arguments can be simulated by passing an object to the function:

function foo(options) {
    options = options || {};
    var a = options.a || 'peanut'; // whatever default value
    var b = options.b || 'butter'; // whatever default value
    console.log(a, b);
}

// ES6 allows automatic destructuring
function foo({a = 'peanut', b = 'butter'} = {}) {
    console.log(a, b);
}

But that does not allow me to accept positional arguments to be passed.

I would like to use ES6 but anything from ES5 would be ok too.

AKG
  • 2,936
  • 5
  • 27
  • 36
  • You can detect the difference between your two scenarios, but ONLY if you know something about the expected type of the arguments and can test that to see which scenario was passed to the function. – jfriend00 Dec 17 '15 at 06:24
  • @jfriend00 thought of checking whether the first argument is an Object to determine if named arguments where used but then what if I need `a` to be an Object as well. That would confuse both scenarios. Any ideas? – AKG Dec 17 '15 at 06:27
  • 2
    Javascript techniques for detecting different variable passing schemes depend upon types and quantity of arguments. You HAVE to design a scheme that lets you distinguish between the different possibilities. You can see [How to Overload Functions in Javascript](http://stackoverflow.com/questions/10855908/how-to-overload-functions-in-javascript/10855939#10855939) for a long discussion of the possibilities. – jfriend00 Dec 17 '15 at 06:30
  • too much sugar. write good functions with explicit inputs. just because something might be possible with a specific language doesn't mean you should do it. – Mulan Dec 17 '15 at 11:54

3 Answers3

4

First of all, I really would recommend to stick with one approach. As you said, use either "named"

function foo({a = 'peanut', b = 'butter'} = {}) {
    console.log(a, b);
}

or positional arguments:

function foo(a = 'peanut', b = 'butter') {
    console.log(a, b);
}

Choose the one that fits your function better, do not mix both.


If you really need both for some reason, standard overloading techniques are available to you. It'll only work properly if your first positional argument is not an object. I would propose one of the following idioms:

function foo(a, b) { // positional is normal case
    if (arguments.length == 1 && typeof arguments[0] == "object")
        {a, b} = arguments[0];

    console.log(a, b);
}
function foo({a, b}) { // named is normal case
    if (arguments.length > 1 || typeof arguments[0] != "object")
        [a, b] = arguments;

    console.log(a, b);
}

and if you need default values, it gets ugly either way:

function foo(a, b) {
    var opts = (arguments.length == 1 && typeof arguments[0] == "object")
      ? arguments[0]
      : {a, b};
    ({a = 'peanut', b = 'butter'} = opts);

    console.log(a, b);
}
Alex Pan
  • 4,341
  • 8
  • 34
  • 45
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

I suppose something like this would work:

function foo(...options){
   if (typeof options[0] === 'object'){
    console.log('expect object', options[0]);
  }else{
    console.log('expect array', options);  
  }
}

foo('peanut', 'butter');
foo({a:'peanut', b:'butter'});
glued
  • 2,579
  • 1
  • 25
  • 40
  • That makes sense. However, if I do `foo('hello')`, I would have `options.length === 1`. Then if I were to check if `options[0]` was an object, then `foo({'whatever': 'object'})` would fail too. Any other ideas? – AKG Dec 17 '15 at 06:35
  • true, you could also check type of the first argument, if it is an object `typeof` – glued Dec 17 '15 at 06:39
  • You're right but what if I expect `a` to be an object anyway? I would then pass an object for `a` but then it would then be wrongly recognized as a named argument set. – AKG Dec 17 '15 at 06:43
  • 1
    Also, `typeof options[0] === 'object'` is going to open up a can of worms: e.g. `typeof null === 'object'` – AKG Dec 17 '15 at 06:44
  • @AKG `typeof new String() === "object"` which is even worse given your parameter requirements – CodingIntrigue Dec 17 '15 at 08:16
1

I don't think there's something built in for that, but this code should work for your case

function foo({a = 'peanut', b = 'butter'} = {}) {
    if (typeof arguments[0] === 'string') {
        return foo({a: arguments[0], b: arguments[1]})
    }
    console.log(a, b);
}
gafi
  • 12,113
  • 2
  • 30
  • 32