Parameter destructuring (equivalent of python's double-splat) - javascript

In python I can pass a dict whose keys match parameters' names with the ** (double-splat) operator:
def foo(a, b):
print (a - b)
args = {'b': 7, 'a': 10}
foo(**args) # prints 3
How to do the same in ES6? This doesn't work:
function foo(a, b) {
console.log(a - b)
}
args = {b: 7, a: 10}
foo(...args)
NB: I'm looking for a solution that wouldn't involve changing the signature of foo because I want it to be used either way (with and without destructuring). So the following should work:
foo(<magic>args);
foo(123, 456);
Bonus question: why is the error message "undefined is not a function"? What exactly is undefined here?
(As answered by #Nina Scholz in the comments, this is because ... requires its argument to have Symbol.iterator, which is not defined for objects).

How to do the same in ES6?
There are no named arguments in JS, only positional ones. So the answer is: you can not.
What you can do is either emulate named arguments via object passing, as #Andy suggested.
function foo({ a, b }) {
console.log(a - b);
}
let args = { b: 7, a: 10 };
foo(args);
Or you could make args to be an array, so you can destruct it into positional arguments.
function foo(a, b) {
console.log(a - b);
}
let args = [10, 7];
foo(...args);
Okay-okay, just for the sake of the argument: it is possible to write a function that will extract parameters of foo and yield properties of args in required order.
function * yolo(args, fn) {
const names = fn.toString().match(/\(.+\)/)[0]
.slice(1, -1).split(',')
.map(x => x.trim());
while (names.length) {
yield args[names.shift()];
}
}
function foo(a, b) {
console.log(a - b);
}
const args = { b: 7, a: 10 };
foo(...yolo(args, foo));
I would not dare to use it in production though.

You need to wrap your args in curly braces, and again in the argument list for the function.
function foo({a, b}) {
console.log(a - b)
}
let args = {b: 7, a: 10}
foo({...args})

Related

Convenient transform of JavaScript method call into function

Is there an easy way to convert a field access or method call into a function that could be passed into e.g. Array.map?
a = {'foo': 1};
b = {'foo': 2};
c = {'foo': 3};
[a, b, c].map(m => m.foo);
Is m => m.foo the best we can do (w/ built-in JS functionality)? Or is there an even shorter way?
One more
[a, b, c].map(({foo}) => foo)

Specify second default argument Javascript

Say I have a function with three arguments, 2 of them having a default value:
function f(a, b=2, c=3){
console.log(a, b, c)
}
If I'm perfectly happy with b's value, is there a way to call f and specify a value for c directly?
In Python I'd do something like f(1, c=5)?
Use cases: if I don't know b's default value or if there are many more arguments it would be cumbersome not to be able to do such a thing
is there a way to call f and specify a value for c directly?
No, JavaScript doesn't have that kind of named parameter.
You can call f using the default for b by giving undefined:
f(1, undefined, 5);
function f(a, b = 2, c = 3){
console.log(a, b, c)
}
f(1, undefined, 5);
Alternately, you can define f differently: Have it accept an object that it destructures into parameters, then call it with an object:
function f({a, b = 2, c = 3}){
console.log(a, b, c)
}
f({a: 1, c: 5});
If you want it to be valid to call it with no object (it's an error with the above), provide a default for the destructured parameter:
function f({a, b = 2, c = 3} = {}){
// -------------------------^^^^^
console.log(a, b, c)
}
You could pass an object with named keys like so:
function f({a, b=2, c=3}){
console.log(a, b, c)
}
f({b: 4})

How to spread an object to a function as arguments?

I have an object an a function which accept arguments, I would like to spread the objects so each property is an argument in that function.
What am I doing wrong in my code?
const args = {
a: 1
b: 2
}
const fn = (a, b) => a + b
// i am trying with no success
console.log(fn(...args))
Although the other answers are correct, they change the function signature to accept an object instead of 2 separate arguments. Here is how to use an object's values as function arguments without altering the function's signature. This requires Object.values (ES 2017) and the spread operator to be available in your runtime.
const args = {
a: 1,
b: 2
}
const fn = (a, b) => a + b
fn(...Object.values(args));
Keep in mind this will work only in your specific case, since Object.values returns the values of all object keys and doesn't guarantee alphabetical sort order. If you want to take only the values of properties which are named a and b, you can map over Object.keys(args) and filter only those values.
You can use ES6 object destructuring on passed parameter and then just pass your object.
const args = {a: 1, b: 2}
const fn = ({a, b}) => a + b
console.log(fn(args))
You can also set default values for those properties.
const args = {b: 2}
const fn = ({a = 0, b = 0}) => a + b
console.log(fn(args))
You need to do it like this
const fn = ({a, b}) => a + b
The other answers are certainly applicable in particular situations, still have some limitations as well. Therefore I'd like to propose a different approach. The idea is to add to the object a method that returns an array of desired parameters in the appropriate order. That method is executed when passed to target function as argument and result destructured with spread operator.
const args = {
a: 1,
b: 2,
argumentify: function () {
return [this.a, this.b];
}
};
const fn = (a, b) => a + b;
console.log(fn(...args.argumentify()));
Benefits of this approach:
1) Does not require changes of the target function's signature, so can be used to ANY function.
2) Guarantees correct order of parameters (which is (as I understand) not guaranteed when spreading object).
3) Can itself be parametrized if needed.
Turn the args to an array should work:
const args = [1, 2]
const fn = (a, b) => a + b
console.log(fn(...args))
See Replace apply() for details.

Pass in any number of arguments into a Javascript function [duplicate]

This question already has answers here:
JavaScript variable number of arguments to function
(12 answers)
Closed 5 years ago.
I want to be able to pass in any number of arguments into a function and then be able to use those arguments later. I know I can pass in default arguments but I don't know what to declare in my function.
For example,
function test(????) {
console.log(a)
console.log(b)
console.log(c)
}
test(a="a", b="b", c="c")
I'm also challenged by being given this function and to make as little changes to the var declarations as much as possible. So here, variables, a, b, and c are declared and I'm not able to edit these variables. I should be able to pass in arguments that will then know to assign itself to these variables.
function test(???) {
var a,b,c
if (a>b){
c = a + b
}
}
test(a=2,b=3)
You actually don't need to define any arguments in the function parameters. All you need to do is access javascript's built in arguments object.
So your code could look like the following:
function test() {
var a = arguments[0];
var b = arguments[1];
var c = arguments[2];
console.log(a);
console.log(b);
console.log(c);
}
test("a", "b", "c");
For reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
Using an array is a good idea, but in the interest of completeness...
The ES6 way!
If you're able to support ES6 features, the spread operator combined with the arguments keyword is a neat way to get around that:
function anyNumber() {
console.log(...arguments); // -> 1 2 3 4
let argsAsArray = [0, ...arguments, 5]; // now they're an array
argsAsArray.forEach(s => console.log(s)); // -> 0 1 2 3 4 5
};
anyNumber(1,2,3,4);
There are a lot of cool things you can do with the spread operator, especially with object and parameter destructuring.
you could pass in an object or an array:
function test1([a, b, c]) {
// ...
}
test1([1, 2, 3]);
function test2({ a, b, c }) {
// ...
}
test2({ a: 1, b: 2, c: 3 });
function test(input) {
console.log(input.a);
console.log(input.b);
console.log(input.c);
}
test({ a: 1, b: 2, c: 3 });
You should use an array or an object.
In that array add as many arguments you want.
function test(arr) {
console.log(arr.a);
console.log(arr.b);
console.log(arr.c);
}
arr = {}
arr.a = "a";
arr.b = "b";
arr.c = "c";
k = test(arr);
Given javascript at updated Question, you can define default parameters within () following function name.
function test(a = 2, b = 3) {
let c;
if (a > b) {
c = a + b
} else {
c = 0
}
console.log(c);
}
test(); // 0
test(4); // 7
See also Can we set persistent default parameters which remain set until explicitly changed?

Does Python have a way to pass arguments through a function

Javascript has a poorly constructed but convenient "arguments" variable inside every function, such that you can pass arguments through a function like so:
function foo(a, b, c) {
return bar.apply(this, arguments);
}
function bar(a, b, c) {
return [a, b, c];
}
foo(2, 3, 5); // returns [2, 3, 5]
Is there an easy way to do a similar thing in Python?
>>> def foo(*args):
... return args
>>> foo(1,2,3)
(1,2,3)
is that what you want?
Yeah, this is what I should have said.
def foo(*args):
return bar(*args)
You don't need to declare the function with (a,b,c). bar(...) will get whatever foo(...) gets.
My other crummier answer is below:
I was so close to answering "No, it can't easily be done" but with a few extra lines, I think it can.
#cbrauchli great idea using locals(), but since locals() also returns local variables, if we do
def foo(a,b,c):
n = "foobar" # any code that declares local variables will affect locals()
return bar(**locals())
we'll be passing an unwanted 4th argument, n, to bar(a,b,c) and we'll get an error. To solve this, you'd want to do something like arguments = locals() in the very first line i.e.
def foo(a, b, c):
myargs = locals() # at this point, locals only has a,b,c
total = a + b + c # we can do what we like until the end
return bar(**myargs) # turn the dictionary of a,b,c into a keyword list using **
How about using * for argument expansion?
>>> def foo(*args):
... return bar(*(args[:3]))
>>> def bar(a, b, c):
... return [a, b, c]
>>> foo(1, 2, 3, 4)
[1, 2, 3]
I think this most closely resembles your javascript snippet. It doesn't require you to change the function definition.
>>> def foo(a, b, c):
... return bar(**locals())
...
>>> def bar(a, b, c):
... return [a, b, c]
...
>>> foo(2,3,5)
[2, 3, 5]
Note that locals() gets all of the local variables, so you should use it at the beginning of the method and make a copy of the dictionary it produces if you declare other variables. Or you can use the inspect module as explained in this SO post.

Categories