Pavlo
Pavlo

Reputation: 1197

What is the practice of multiple expressions in an arrow function without curly brackets

I like to explore the limitations of JavaScript, and wondered if you could use arrow functions where the last expression would be the return statement.

Luckily this is possible, but setting variables in the same scope has some downsides.

[1,2,3,4,5].reduce((sum, element) => (const doubleSum=sum+sum, doubleSum+element));

This returns unidentified identifier (referring to doubleSum)

The solution is to write it like this instead

let doubleSum; 
[1,2,3,4,5].reduce((sum, element) => (doubleSum=sum+sum, doubleSum+element));

So now without curly backet i am able to get the sum of sum+sum+element by having two expressions within the function. This isn't a great example usage, but it shows how to use 2 expressions without curly bracket.

The result of this would be 57 because it only returns doubleSum+element per iteration. This would work great for calculations if you wanted to split it up into more readable parts.

Another example is to log the results for each iteration without having to use curly brackets

[1,2,3,4,5].reduce((sum, element) => (console.log(sum), sum+sum+element));
//1
//4
//11
//26
//57

I really like this syntax, but i wonder if it is bad practice since it isn't mentioned on Arrow functions

Upvotes: 3

Views: 2084

Answers (4)

Zdeněk Jelínek
Zdeněk Jelínek

Reputation: 2913

The , you speak of is known as comma operator (MDN entry).

This operator comes from C language where the use-cases were usually one-line declarations of multiple variables (var i, j, k) and specific macros (which are a pain).

Most modern languages dropped this operator in favor of higher abstractions.

But even if we skipped the history, I would assume that among the primary goals of best practices would be the attribute of readability. There's definitely not a goal of saving a few keystrokes.

Now compare:

let doubleSum; 
[1,2,3,4,5].reduce((sum, element) => (doubleSum=sum+sum, doubleSum+element));

and

[1,2,3,4,5].reduce((sum, element) =>
{
    const doubleSum = sum + sum;
    return doubleSum + element;
});

One of the samples declares doubleSum as closed-over variable used for all reduce iterations while the other declares doubleSum as a variable local to each iteration. Which one do you think shows the intent better? Which one would be easier to refactor without the fear of breaking some odd case?

With the logging example, this is not as clear. However, it is generally considered a good practice to put each side-effect (such as logging a message) into a separate statement (a line ending with ;).

Imagine this:

someArray.map((element) => (console.log(element), doSomething(element)));

However, for some reason, doSomething doesn't work the way you expected it to. Let's add some logging:

let intermediate;
someArray.map((element) => (
    console.log(element),
    intermediate = doSometing(element),
    console.log(intermediate),
    intermediate));

We're back at that weird scope issue with intermediate. This change may even not be so easy to do - you want to do something in the map callback, but in fact you have to create a variable outside its scope to be able to do it. Now imagine that the complexity of the mapping callback would increase to have several such bound variables. Or perhaps to have if/else branches with more variables. And then you'd perhaps like to extract it to a separate function: Now you need to reason about all the bound variables once again.

How about using blocks?

someArray.map((element) =>
{
    console.log(element);
    doSomething(element);
});

becomes

someArray.map((element) =>
{
    console.log(element);
    const intermediate = doSometing(element);
    console.log(intermediate);
    return intermediate;
});

Now, we didn't have to edit anything else than the inner block of map callback - the change was way easier to do and to reason about. Introducing more variables? No problem, they all stay in the callback. Extracting the callback as a separate function? No problem either. The intent of this code is clear.

Upvotes: 2

Jonas Wilms
Jonas Wilms

Reputation: 138267

You cant declare variables inside expressions. But you could build up a function expression and immeadiately call it (called IIFE) so you got local parameters:

 [1, 2, 3, 4].reduce((sum, value) => (double => double + value)(sum + sum));

If that is useful is anorher thing.

Upvotes: 1

fethe
fethe

Reputation: 697

I think what you're trying to achieve might get the code hard to read. There's nothing wrong with using curly braces with arrow function. If you want to keep your reduce callback clean you might want to do something like this:

const doubleSumAndAddElement = (sum, element) => {
    console.log(sum);
    return 2 * sum + element;
}

[1,2,3,4,5].reduce(doubleSumAndAddElement);

Upvotes: 0

Bergi
Bergi

Reputation: 664434

I wonder if it is bad practice

Yes, declaring the doubleSum outside of the scope of the array function definitely is a bad practice. Don't do that.

Another example is to use console.log without curly brackets. I really like this syntax, but it isn't mentioned on Arrow functions

Yes, that's that because it's not specific to arrow functions. You're just using the grouping syntax and the comma operator here. Some similar usage can be found in the Processing and then returning usage example there.

Upvotes: 3

Related Questions