[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 it's interesting since the generality is not obvious (to me) [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 it's interesting since the generality is not obvious (to me)> 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 array.
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.
That note is misleading.
> 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.
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.)
In TypeScript, you might have an array of multiple types (e.g. `Array<A | B>`), and use a `filter` call to only keep the `A`s. However, in many situations TypeScript can't figure this out and the resulting array type is still `Array<A | B>`. However, when you just use `flatMap` to do nothing more than filtering in the same way, TypeScript can determine that the resulting type is just `Array<A>`. It's a bit unfortunate really - `filter` is faster and more readable, but the ergonomics of `flatMap` type-wise are so much nicer! Just some interesting trivia.
[0]: https://github.com/microsoft/TypeScript/issues/16069#issueco...
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<A, B>(cb: A => A is B)
:/
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): value is string => typeof value === 'string');
// arrayOfString: string[]
This example in Typescript playground: https://www.typescriptlang.org/play?#code/MYewdgzgLgBAhgJwXA...[1]: https://www.typescriptlang.org/docs/handbook/advanced-types....
filter<U extends T>(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
ls.flatMap(x => {
if (x < 0) {
return []
} else if (x == 0) {
return [0]
} else {
return [Math.sqrt(x), -Math.sqrt(x)]
}
})
gives you all the real square roots from the original list, doing the mapping, flattening, and filtering all in one function call. ls.filter(o=>0<o).map(o=>o||[Math.sqrt(x, -Math.sqrt(x)])nothing, but they do have some relationship to 0, "" and Promise.resolve() - the array is handling the logic that will make the results be combined, not the doubling part
You do have to make sure that your implementation of list is extremely efficient on zero and one element lists (ideally it generates no garbage at all in those cases) otherwise as other commentators have pointed out you'll have a lot of GC pressure.
And even though the transducer itself is `x -> List(x)` note that the `List` is only produced as an intermediate step and doesn't need to exist in the final product. You could apply a `x -> List(x)` to a generator for example and just "absorb" the list back into the resulting generator.
Or to put it another way, if I reviewed code where someone used flatMap for anything other than lists of lists I'd be likely to suggest filter/map or reduce or some other convenient equivalent depending on the purpose of the code.
Something like Ruby's filter_map[0] would do the job, although not with this particular example (because 0 is truthy in Ruby).
[0] https://ruby-doc.org/core-3.1.0/Enumerable.html#method-i-fil...
/** Return this symbol to skip the current value. */
const SKIP = Symbol("mapFilter.SKIP");
/**
* @template T, R
* @param {T[]} array
* @param {(SKIP: Symbol, currentValue: T, index: number, array: T[]) => R|SKIP} callback return `SKIP` to filter out an element
* @param {number} [begin] defaults to 0
* @param {number} [end] defaults to `array.length`
* @returns {R[]}
*/
function mapFilter(array, callback, begin = 0, end = array.length) {
const ret = [];
for (let i = begin; i < end; i++) {
const v = callback(SKIP, array[i], i, array);
if (v !== SKIP) ret.push( /** @type {R} */ (v));
}
return ret;
};
Here. Less than ten lines without JSDoc type annotations, twenty with them. It lets you slice, map and filter all in one call without allocating intermediate arrays like you would when chaining them, making it almost as fast as a plain for-loop. It's also easy to turn it into an in-place version, removing even the array allocation overhead. [1, 2, 3, 4, 5].reduce((x, y) => y % 2 === 1 ? [...x, y * 2] : x, []) [1, 2, 3, 4, 5].reduce((x, y) => { if (y % 2 === 1) x.push(y * 2); return x; }, [])
I've many times wished that push() would just return the array, it would make reduce() far easier for this sort of use case. x.concat([y*2])
would return the array (but makes a duplicate)Anyway, I find this to be a whole lot more sensible:
x=[];
for(y of [1,2,3,4,5]){
if(y%2===1)x.push(y*2)
}
Or even! y=[1,2,3,4,5];
x=[];
// map reduce/flatmap/map/filter etc omg wtf
for( i=0; i < y.length; i++ ){
if( y[i]%2 === 1 ){ x.push( y[i] * 2 ); }
}
I cant even tell what language this is but there is nothing here that needs fixing.[1, 2, 3, 4, 5].reduce((acc, n) => n % 2 === 1 ? acc.push(2*n) : acc, [])
[1, 2, 3, 4, 5].reduce((acc, n) => n % 2 === 1 ? acc.concat([2*n]) : acc, []) [1, 2, 3, 4, 5].reduce((acc, n) => (n % 2 ? acc.push(2*n) : null, acc), []) let input = [1, 2, 3, 4, 5], output = [];
for (let i = 0; i < input.length; ++i) {
let n = input[i];
if (n % 2) output.push(2*n);
}
return output;
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.FWIW, it's 2022:
const output = [];
for (const n of [1, 2, 3, 4, 5]) {
if (n % 2) output.push(2 * n);
}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; [1, 2, 3, 4, 5].reduce((acc, n) => n % 2 === 1 ? [ ...acc, 2 * n ] : acc, []) (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!