Here’s the fun part that I suspect many here are forgetting: if you want to write the function
body, it will probably (or at the very least
can) look very similar!
function flat() { // type FlatArr<Arg extends unknown[]> =
let [first, ...rest] = this;
return (this.length > 0) ? // Arg extends [infer First, ...(infer Rest)] ?
Array.isArray(first) ? // First extends unknown[] ?
[...first, ...flat(rest)] : // [...First, ...FlatArr<Rest>] :
[first, ...flat(rest)] : // [First, ...FlatArr<Rest>] :
[]; // [];
}
If you want to unternarise it:
function flat() {
if (this.length > 0) {
let [first, ...rest] = this;
if (Array.isArray(first)) {
return [...first, ...flat(rest)];
} else {
return [first, ...flat(rest)];
}
} else {
return [];
}
}
I still wouldn’t call it
basic TypeScript, but it’s not conceptually
that advanced, you just need to know about infer and extends.
Now in reality, Array.prototype.flat has a more complex definition, partly because (like most of Array’s methods) the method is generic (it works on array-like objects that have a length property and numeric indexing), and partly because of the depth parameter. From lib.es2019.array.d.ts:
type FlatArray<Arr, Depth extends number> = {
done: Arr;
recur: Arr extends ReadonlyArray<infer InnerArr> ? FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][Depth]>
: Arr;
}[Depth extends -1 ? "done" : "recur"];
interface ReadonlyArray<T> {
// [Snipped: flatMap, and flat’s doc comment]
flat<A, D extends number = 1>(
this: A,
depth?: D,
): FlatArray<A, D>[];
}
// [And repeated for interface Array<T>.]
Ouch. Don’t like the { done, recur }[Depth extends -1 ? "done" : "recur"]
at all, no idea why it wasn’t written as `Depth extends -1 ? Arr : Arr extends ReadonlyArray<…`. And as for hard-coding support for depths up to 20 then bailing… probably pragmatic, it’s
possible to support all values, but rather messy:
https://stackoverflow.com/q/54243431.