DEV Community

Discussion on: The Most POWERFUL [JavaScript] Function

Collapse
 
snickdx profile image
Nicholas Mendez

Indeed JavaScript has enough syntax to implement a solution in multiple ways. However, in writing clean code the solution that is readable and optimal is most preferred. For loops would work fine for most if not all of these use cases.

Relevant Video: youtube.com/watch?v=qaGjS7-qWzg

Collapse
 
insidewhy profile image
insidewhy

Verbose code is not only boring to write, it can hamper understanding. I think most teams will opt to balance verbosity and ease of understanding. I haven't worked with developers who would favour loops over a functional approach in many years. Although it is necessary when you're writing a framework due to v8's poor optimisation of closures, in other cases performance doesn't matter so much.

Collapse
 
cleancodestudio profile image
Clean Code Studio

I've actually seen this video before. These developers are probably pretty descent developers if they come from Google (I'm at Amazon and will personally tell you that FAANG developers are not always spectacular - they won't be bad, but it's still not common to see spectacular at FAANG. You'll run into great ones more often, but on average, most FAANG developers are simply pretty good and very few are spectacularly good).

The next thought I'd bring up is that for loops in general leave a lot of room for unintended mutations to occur. One of the many benefits of reduce is that it is pure in form (given you don't use an implicit return and intentionally or unintentionally pull in extraneous state that then neglects this point).

One of, if not the, number one cause of JavaScript bugs is unintended mutations.

I do like there idea that the callback should be the second parameter.

As far as readability goes, I'm not onboard with the idea that reduce is less readable. I will say it takes experience using reduce for it to come across as clean as it is to those who are familiar with functional programming.

Once you are familiar with the syntax, I'd say reduce is as readable if not more readable than loops.

const flatArr = []

for (const elem of arr) {
    if (Array.isArray(elem)) 
       flatArr.push(...elem)
    else
       flatArr.push(elem)
}
Enter fullscreen mode Exit fullscreen mode

This example they show, as they pointed out, has a mutation. I agree that within a given modularized block, mutations aren't the most tragic thing in the world.

On the other hand, it is a negative - but it's a trade off for improved readability. If the readability is the same then you don't want to make that trade off.

const flatArr = []

flatArr.reduce((flattened, elem) => [
           ...flattened,  
           ...(Array.isArray(elem) ? elem : [elem])
        ]
, [])
Enter fullscreen mode Exit fullscreen mode

I'd argue by simply implementing a cleaner reduce implementation that we are able to have the best of both worlds.

All we're doing is inversing how we handle the element if it is an array. If the elem is not an array we wrap it in brackets making it an array otherwise we leave it be. Then we call the spread operator knowing that we'll have an array either way.

We don't have to make the trade off of potential mutations for increased readability.

Additionally, we could take this a step further and abstract away the functionality of arrayifying an element if it isn't already an array into it's own one line pure function.

const flatArr = []
const toArray = item => Array.isArray(item) ? item : [item]
flatArr.reduce((flatten, elem) => [...flatten, ...toArray(element)],  [])
Enter fullscreen mode Exit fullscreen mode

Now, using a simple one liner toArray function that is pure, we have removed all of our indentations and made the lines visually appealing by proceeding the length of each next line by more characters.

If you personally ask me which is more readable between

const flatArr = []

for (const elem of arr) {
    if (Array.isArray(elem)) 
       flatArr.push(...elem)
    else
       flatArr.push(elem)
}
Enter fullscreen mode Exit fullscreen mode

and

const flatArr = []
const toArray = item => Array.isArray(item) ? item : [item]

flatArr.reduce((flatten, elem) => [
   ...flatten, ...toArray(element)
],  [])
Enter fullscreen mode Exit fullscreen mode

I'm going with the ladder.

In the ladder example we have variables, functions, parameters, and output.

In the first example we have language keywords like of which is often confused with in or simply not used and instead a longer form implementation is used to get the for loop working properly.

In the first examples we have two paths that tree out, that's an extra dependency - if else.

If this then mutate this variable into this, else mutate this variable into that.

In our second example, we are mapping each element of our array using the toArray function. That's not if this, else that. We are saying this will happen. Next this will happen.

We don't have to worry about accidentally treeing out the wrong direction and unintentionally mutating our variable which would not necessarily throw an error letting us know we messed up.

In our second example, if we don't map the item correctly, we'll immediately get an error - you can't spread a non spreadable thing in JS. If we don't properly map our element into the toArray function, then we don't have an array. Error immediately - we fix the bug, no treeing out, no unintended mutations, and once you spend the time to understand what's going on I would argue it IS much more readable to use reduce than the for loop.

Collapse
 
cleancodestudio profile image
Clean Code Studio • Edited

Oooo, also an after point, this example above is a great show case of why the ternary should be used in place of if else when possible.

If else trees out the possible states. Ternaries do not. This was an interesting one - thanks for your comment @nicholasmenez , it has me thinking deep into the details.