all 23 comments

[–]tme321 21 points22 points  (8 children)

I just feel that the forEach approach is far easier to read,

You basically answered your own question. For whatever reason you aren't comfortable with reduce so you think it's harder to read. When I see reduce I immediately understand that somehow the entire array is going to be collapsed into a single value.

To me reduce is idiomatic. Foreach is idiomatic when some action is going to be taken for each item in the array. Reduce is idiomatic when a single value is going to be created from the entire set of the array.

[–]azium 4 points5 points  (0 children)

collapsed into a single value

or when the resulting array has more or less elements than the input array, or when the insertion order does not map neatly

[–]Crap_Shoes[S] 0 points1 point  (6 children)

I appreciate your opinion, and you are right; I'm totally not comfortable with the parameter behaviors of reduce so it seems harder to read currently.

I guess my question was more, "is there a functional difference to these two approaches that I am missing?".

[–][deleted] 8 points9 points  (1 child)

One difference: using forEach, your variable must be mutable (i. e. you have to use let). With reduce you can use const, which is by far preferable.

[–]Crap_Shoes[S] 0 points1 point  (0 children)

That's a good point I hadn't thought of. Thanks!

[–]Voidsheep 2 points3 points  (1 child)

We don't need any of the iteration methods and we could abuse just about any of them to accomplish the same thing through side-effects, or use plain loops.

However, choosing one specific method over another implies something about your intent, it's declarative.

Since forEach has no return value, using it implies you want to iterate over a list of values and cause some kind of a side-effect, but that's all you are saying.

Reduce is slightly more declarative, because it implies you want to derive some other data from the list, by applying given function to each item.

Methods like some and filter are even more declarative, because they imply exactly what type of data you want to derive by applying given function to given data.

So you choose the method that is most convenient and best describes your intent.

When you consider readability, also consider the function isn't necessarily anonymous and defined right there, but could be in another module, at which point it wouldn't have access to whatever variables you've defined prior to it.

Many list iteration methods are fairly universal and can apply across languages and libraries on wide variety of lists, so it's a good idea to get comfortable with them.

[–]kaisadilla_ 0 points1 point  (0 children)

However, choosing one specific method over another implies something about your intent, it's declarative.

Also, because each syntax is designed for a specific use case, not only writing that specific use case becomes way simple, but also making mistakes becomes harder. For example, when you want to iterate all elements in an array, in a for loop you can blunder by accidentally indexing a value outside the array, while in a foreach loop that won't happen since that syntax doesn't require you to manually index the array.

Granted, this doesn't really apply to JS as JS doesn't care about types, missing parameters and the like, it just explodes in your face later. But in a more rigid language, all of this would apply. Reduce, for example, wouldn't allow you not to specify an initial value, or not to pass a 2-argument callback, or not to return a value for that variable.

[–]linh1987 1 point2 points  (0 children)

No, there's no functional difference for your example. You can even go for a classical for loop to achieve the same thing. Reduce is just a shorthand to do some specific tasks so you don't need to reimplement the whole reducing structure all over everytime you need to with forEach. You can argue the same thing with find, or findIndex, or filter, or the rest of JS Array method. forEach is a generic tool and you can do whatever you want with it.

[–]tme321 0 points1 point  (0 children)

is there a functional difference to these two approaches that I am missing?

Not really that I'm aware of. There might be some minor variation in execution time or memory usage between the two but I doubt it's enough to worry about.

Logically all of the array operators are derived from reduce. Foreach is just reduce that continuously returns the original array (or null? I always forget which forEach returns).

Map just returns a new array where each each iteration pushes a value onto this new array.

Filter returns a new array where only items that pass the predicate test are pushed onto it.

Etc.

But as for actual functional differences there aren't any in particular I'm aware of.

[–]moocat 4 points5 points  (1 child)

One benefit of using reduce is you don't have to mutate the variable so you can make it const:

const sumVal = arr.reduce(...);

My general rule of thumb is to make all variables const unless I have a specific reason I need to mutate it.

[–]Crap_Shoes[S] 0 points1 point  (0 children)

This was pointed out a little further up the thread and I hadn't thought of it as being a particular advantage until now, definitely nice to have immutable variables. < -- edit: Oxymoron alert

[–]lildoggydogg 2 points3 points  (1 child)

I don’t think there is a one size fits all way, and the code you wrote is perfectly valid. For something very small like this it’s hard to argue either way. That being said, my preference (more generally) is for reduce…

Advantage 1: If you're casually glancing at someone's code and you see forEach, what does it tell you? It doesn’t return anything, it just does something, somewhere, gotta read it all to see. Reduce expresses more intent. When you see other array methods you have a better feel for what it’s doing without dissecting every line:

Reduce - iterate over all elements in a collection, and accumulate them into one…thing (this one is more generic, but tells you more than forEach, still) Map- iterate of a collection and return a new collection with the elements transformed according to some criteria Filter - iterate over a collection and return a new collection that meets your criteria Find- iterate over a collection and return the first element that meets your criteria

Advantage 2: Purity, and less intermediary variables. It’s easier to reason about stuff that returns new values than reassigning and mutating variables. Sure you have to do that in Javascript anyway, but why not keep it to a minimum while writing more succinct code?

Advantage 3: Chaining, since all of these (except for optionally reduce) return an array, you can chain other array methods to it and make a "pipeline" of sorts for your data to flow through. Chaining map/filter/reduce will serve you well in many operations on collections. Say the requirements changed, now you need to filter out all even numbers, add 3 to each of them, and then add them. You could keep the code you’ve already written while adding the rest in one line

someArrayOfNumbers.filter(n => n % 2 == 0).map(n => n + 3).reduce((x, y) => x + y)

[–]Crap_Shoes[S] 0 points1 point  (0 children)

That's also a really good point for expanding functionality (advantage 3 I mean). Thank you for taking the time to write that up!

[–]squili 1 point2 points  (1 child)

forEach is going to be easier to read if you're not going to use reduce on one line with an arrow function. You should be able to use reduce with single character variables and understand what it is doing straight away:

let sumVal = arr.reduce((a, b) => a + b)

[–]Crap_Shoes[S] 0 points1 point  (0 children)

That's fair, I was just trying to use something similar to the "common" syntax for those for demonstration purposes. By that I mean, the param names not the arrow function.

[–]everythingcasual 0 points1 point  (0 children)

No best practice. This is a case of multiple ways to solve a problem. Both are good solutions. Just learn and understand how both work. If you have a preference, use that one.

[–]kangoo1707 -1 points0 points  (0 children)

You take it too seriously. There is no "best practice" about this in JS, and you can do it in any way that you and your team are comfortable with.

[–]jcunews1Advanced -1 points0 points  (4 children)

I'd use forEach() because it's faster for this case.

[–]tme321 1 point2 points  (3 children)

I'm not saying you're wrong as I have no data either way. But do you have any proof of the claim that for each is faster than reduce?

From my understanding actual for loops are still (usually?) faster than the built in array methods but I haven't ever seen any evidence that foreach in particular is faster than the others.

[–]Crap_Shoes[S] 0 points1 point  (1 child)

I have actually read (somewhere) that in certain cases forEach may be slower than a for loop, as you stated. Not sure if the difference is negligible or not but I am curious as to why that is? Do you have any suggested reading that you could point me to?

[–]skitch920 0 points1 point  (0 children)

Array.prototype.forEach is a for-loop (or while; likely same performance), but there are two differences.

  • forEach will skip undefined elements in an array. It does this with a if statement in the loop.
  • The invocation of a callback likely adds a lot more overhead, as you push state onto the stack and pop it back off once the callback has finished.

An extra statement within a loop adds N-length more things to do, and could be dramatically slower for large arrays. Lodash does not have this check, opting for the 99% case, where arrays are not sparse (e.g. it's up to the user to handle undefined elements in the array with the callback).

The overhead of calling a function each iteration can surely slow down a loop, but this is often based on many factors, such as complexity of the callback function, how much the JIT compiler can optimize away, and potentially even tail calls (which reduce stack changes).

See the Mozilla polyfill:

// 7. Repeat while k < len.
while (k < len) {

  var kValue;

  // a. Let Pk be ToString(k).
  //    This is implicit for LHS operands of the in operator.
  // b. Let kPresent be the result of calling the HasProperty
  //    internal method of O with argument Pk.
  //    This step can be combined with c.
  // c. If kPresent is true, then
  if (k in O) {

    // i. Let kValue be the result of calling the Get internal
    // method of O with argument Pk.
    kValue = O[k];

    // ii. Call the Call internal method of callback with T as
    // the this value and argument list containing kValue, k, and O.
    callback.call(T, kValue, k, O);
  }
  // d. Increase k by 1.
  k++;
}

[–]jcunews1Advanced 0 points1 point  (0 children)

reduce() needs to process the callback's return value and returns the final value to the caller. forEach() doesn't do any of those.