Created
February 29, 2024 16:13
-
-
Save karol-majewski/fb52a6f2afee10c5eaaf2af04cfbfc1b to your computer and use it in GitHub Desktop.
Preserving tuple length
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// #region Necessary types | |
type NonEmptyArray<T> = [head: T, ...T[]]; | |
type Tuple<T> = [T, T]; | |
declare function head<T>(collection: [T, ...T[]]): T; | |
// #endregion | |
const cloudflareDNS: NonEmptyArray<string> = ["1.1.1.1"]; | |
const googleDNS: Tuple<string> = ["8.8.8.8", "8.8.4.4"] | |
// ✅ These Just Work™️ | |
head(cloudflareDNS); // ✅ | |
head(googleDNS); // ✅ | |
// ⛔️ But you may want to massage your data first, and these won't work | |
head(cloudflareDNS.map(address => ({ target: address }))); | |
head(googleDNS.slice().sort()); | |
head(cloudflareDNS.concat()); | |
head(cloudflareDNS.concat([])); | |
head(cloudflareDNS.concat("1.0.0.1")); | |
head(cloudflareDNS.concat(["1.0.0.1"])); | |
head(cloudflareDNS.concat(googleDNS)); | |
const tuple: Tuple<string> = googleDNS.map(address => "Your address:" + " " + address) | |
// declare global { | |
// interface Array<T> { | |
// /** | |
// * If two arrays are concatenated, and at least one of them has elements, then the array created by concatenation must also have elements. | |
// */ | |
// concat(this: NonEmptyArray<T>, ...items: ConcatArray<T>[]): NonEmptyArray<T>; | |
// concat(...items: NonEmptyArray<ConcatArray<T>>): NonEmptyArray<T>; | |
// concat(...items: NonEmptyArray<T | ConcatArray<T>>): NonEmptyArray<T>; | |
// /** | |
// * When called on a tuple, the output has the same length as the input. | |
// * Make sure to allow readonly arrays too. | |
// */ | |
// map<U>(this: T[] | [T], callbackfn: (value: T, index: number, array: this) => U, thisArg?: any): { [index in keyof this]: U; }; | |
// map<U>(this: readonly T[] | readonly [T], callbackfn: (value: T, index: number, array: this) => U, thisArg?: any): { [index in keyof this]: U; }; | |
// /** | |
// * When called on a tuple, the output has the same length as the input. | |
// */ | |
// slice(): this | |
// } | |
// } | |
// ⛔️ These also don't work out of the box | |
declare const salaries: Map<string, number>; | |
const name = "Bob"; | |
if (salaries.has("Bob")) { | |
const salary: number = salaries.get(name); | |
} | |
declare global { | |
interface Map<K, V> { | |
has<T extends K>(key: T): this is { get(key: T): V } & Map<K, V>; | |
} | |
} | |
// #region Misc | |
export { } | |
export const hasElements = <T,>(collection: T[] | readonly T[]): collection is NonEmptyArray<T> => collection.length > 0; | |
// Takeaways | |
// | |
// • Leverage mapped tuple types https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-1.html | |
// • This can be risky (also the reason it doesn't work for maps) | |
// • Remember about including overloads for readonly arrays | |
// • https://gist.github.com/karol-majewski/bd00cbe38d8c31b7af87f66c896796f9 | |
// #endregion |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TypeScript Playground