Live data from Hacker News

Flatten Arrays in Vanilla JavaScript with Flat() and FlatMap()

wisdomgeek.com

81–90 of 97 posts

Re: Flatten Arrays in Vanilla JavaScript with Flat() and FlatMap()

#81
post #29

Earlier quoted context omitted.

For those wondering at home, the reason you shouldn't do this is immediately spelled out in the Mozilla docs for flatMap: > Note, however, that this is inefficient and should be avoided for large arrays: in each iteration, it creates a new temporary array that must be garbage-collected, and it copies elements from the current accumulator array into a new array instead of just adding the new elements to the existing a…

Sure. I'm not suggesting it be used to this effect; I'm noting the generality as an interesting point.

I assumed you were aware of the issues, which is why I said those at home who might just take it at face value as a neat pattern. :)

I personally do actually use flatMap sometimes as outlined in your example - perf is an after issue and I'll refactor if the concise code isn't worth the cost.

Re: Flatten Arrays in Vanilla JavaScript with Flat() and FlatMap()

#82
post #78

Earlier quoted context omitted.

It still creates a temporary [x, 2*x] array for every element though. This is an unavoidable problem with flatMap, while reduce can easily be changed to reuse the same accumulator array, making it twice as fast as flatMap and almost as fast as the simple for-loop approach.

My preference for when I have to do multiple operations on the same array (like filter then map) is "reduce". It's the most straightforward way other developers will understand what's going on.

Not that I disagree with your preference, in fact I share it. But this:

> It's the most straightforward way other developers will understand what's going on.

… has been the opposite of my experience. Both on the job (where I’ve always conceded to team preference for imperative loops) and observing the community (hating on reduce is a whole meme on JavaScript/TypeScript Twitter, and the contrary meme has never shown up at least on my feed).

And I honestly understand why it’s not very popular. Reduce/fold is a very FP concept which isn’t particularly idiomatic in real world JS. When I learned and embraced it (myself coming from a JS background), it took dozens of real uses before I felt like I had committed to my own memory what’s actually happening. And by then I think I was writing Clojure.

Re: Flatten Arrays in Vanilla JavaScript with Flat() and FlatMap()

#83

Something that people may not see immediately is that flatMap is more general than map and filter. Say, for a contrived example, that you'd like to filter out the even numbers in an array, and then double the odd numbers that remain. Instead of: [1, 2, 3, 4, 5].filter(n => n % 2 === 1).map(n => n * 2) You can do: [1, 2, 3, 4, 5].flatMap(n => n % 2 === 1 ? [n * 2] : []) Again, this is a contrived example, but I think…

Yes, but you could also use `fold`^H^H^H^H`reduce`. [1, 2, 3, 4, 5].reduce((acc, n) => n % 2 === 1 ? acc.push(2*n) : acc, [])

Hilariously enough, I think reduce would be much more palatable to JS devs if it was named fold. Not because the familiarity would jump out but because it’s a much more evocative name that could make visualizing its behavior more intuitive.

Re: Flatten Arrays in Vanilla JavaScript with Flat() and FlatMap()

#84

Earlier quoted context omitted.

Yes, but you could also use `fold`^H^H^H^H`reduce`. [1, 2, 3, 4, 5].reduce((acc, n) => n % 2 === 1 ? acc.push(2*n) : acc, [])

Push returns the length of the array, though, so that won't work.

Comma operator to the rescue:

    (acc.push(2*n), acc)
In an expression position, the push statement will be executed, then its return will be discarded, and the final expression will be the result of the expression. Is it “better”? Almost certainly not. But it lets you stay in expression syntax while executing statements. (Much more useful for logging than meaningful runtime side effects IMO, but I think it should be more widely known in general.)

Edit: and I’m glad to see another reference to it down thread!

Re: Flatten Arrays in Vanilla JavaScript with Flat() and FlatMap()

#85
post #63

Earlier quoted context omitted.

Obviously you can alternately write: let input = [1, 2, 3, 4, 5], output = []; for (let i = 0; i But in some circumstances the other style can be more convenient / legible. The immediate question was about pushing to an array and then returning the array, for which the comma operator can be handy.

No argument that the comma operator is a neat trick when you need it. FWIW, it's 2022: const output = []; for (const n of [1, 2, 3, 4, 5]) { if (n % 2) output.push(2 * n); }

Minority opinion: please `let` your mutable references. I know `const` doesn’t signal immutability, but we as humans with eyeballs and limited attention span certainly benefit from knowing when a value might change at runtime.

Re: Flatten Arrays in Vanilla JavaScript with Flat() and FlatMap()

#86
post #78

Earlier quoted context omitted.

It still creates a temporary [x, 2*x] array for every element though. This is an unavoidable problem with flatMap, while reduce can easily be changed to reuse the same accumulator array, making it twice as fast as flatMap and almost as fast as the simple for-loop approach.

My preference for when I have to do multiple operations on the same array (like filter then map) is "reduce". It's the most straightforward way other developers will understand what's going on.

> My preference for when I have to do multiple operations on the same array (like filter then map) is "reduce". It's the most straightforward way other developers will understand what's going on.

There are definitely operations that are more intuitive as reduces, but any sequences consisting of exclusively filter, map, and/or flatMap are, IME and IMO, about the worst candidates.

(That said, because the JS filter, map, etc. operations are eager rather than lazy, sequenced operations produce intermediate arrays that may be undesirable especially with large datasets, so reduce can be desirable even without being more clear in intent.)

Re: Flatten Arrays in Vanilla JavaScript with Flat() and FlatMap()

#87

Earlier quoted context omitted.

Which input method are you using that double quotes are ” (U+201C) instead of " (U+0022)?

Wow, no clue. That was on mobile... web app or Octal (native app). Stock iOS English (US) keyboard.

Not that you have to, but just FYI you can disable "smart" punctuation in iOS Settings: General -> Keyboard -> Smart Punctuation. I don't know why they omitted the quotes around smart there.

Re: Flatten Arrays in Vanilla JavaScript with Flat() and FlatMap()

#88
post #7

I've made it a habit to check Mozilla's JS docs once in a while for functions like Flat() and FlatMap(). Sometimes if I find myself reaching for underscore/lodash, I'll check Mozilla docs first to see if there's some new function that can let me omit using lodash. I'm often delighted to find new convenience functions I can just use without adding another dependable.

If you're chaining, you're still MUCH better off with Lodash. Native functions iterate once per function. Lodash uses an iterator and only loops through the list once.

Re: Flatten Arrays in Vanilla JavaScript with Flat() and FlatMap()

#89
post #72

Earlier quoted context omitted.

I wonder if it is possible to add a feature to Typescript to help with this: You could potentially add a syntax for type guards function types, then add a signature to filter that accepts a type guard and returns an array of the guarded types. Shouldn't be too much of a stretch given that we have type guards. The syntax is a bit annoying... should be something like filter (cb: A => A is B) :/

You can use a type guard[1] as an argument to Array.filter, but the function has to be explicitly typed as such. I don't know why the type isn't narrowed in Array.filter like it is in if statements without this weird workaround. const array: (number | string)[] = []; const mixedArray = array.filter(value => typeof value === 'string'); // mixedArray: (number | string)[] const arrayOfString = array.filter((value): valu…

Oh so there is an overload!

filter(pred: (a: T) => a is U): U[];

Additionally, getting TS better at inferring type guards is an open issue (literally): https://github.com/microsoft/TypeScript/issues/38390

Re: Flatten Arrays in Vanilla JavaScript with Flat() and FlatMap()

#90
post #63

Earlier quoted context omitted.

No argument that the comma operator is a neat trick when you need it. FWIW, it's 2022: const output = []; for (const n of [1, 2, 3, 4, 5]) { if (n % 2) output.push(2 * n); }

Minority opinion: please `let` your mutable references. I know `const` doesn’t signal immutability, but we as humans with eyeballs and limited attention span certainly benefit from knowing when a value might change at runtime.

Disagree: virtually everything in JS is mutable, so this almost means "never use the `const` keyword". Pretending that the `const` keyword means something that it doesn't makes things harder for my limited human mind to understand, not easier. Plus using `let` inappropriately makes my linter yells at me, and I usually like to just do whatever my linter tells me.

Anyway, I use TypeScript, so if I really want to assert that my array is immutable (as immutable as stuff in JS-land gets anyway) I just write:

  const input: readonly number[] = [1, 2, 3, 4, 5];
or even

  const input = [1, 2, 3, 4, 5] as const;
Post reply on HN