3 ms·
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 simil
by chrismorgan 11mo ago
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 https://stackoverflow.com/q/54243431.
- pcthrowaway 11mo agoNotably, the provided type for Array.prototype.flat doesn't actually provide the flat type for arrays of a known structure. In other words, if you flatten `[string, [string, number]]` my example would give you `[string, string, number]` whereas the one in lib.es2019.array.d.ts would give you `(string | number)[]` (my example, on the other hand, would only flatten a depth of 1, though a completely flattened type instead of just depth 1 could be represented by changing `[...First, ...FlatArr<Rest>] :` to `[...FlatArr<First>, ...FlatArr<Rest>] :` I haven't tried supporting a depth param, but I suspect it's possible.