Skip to content

Instantly share code, notes, and snippets.

@karol-majewski
Created February 29, 2024 16:13
Show Gist options
  • Save karol-majewski/fb52a6f2afee10c5eaaf2af04cfbfc1b to your computer and use it in GitHub Desktop.
Save karol-majewski/fb52a6f2afee10c5eaaf2af04cfbfc1b to your computer and use it in GitHub Desktop.
Preserving tuple length
// #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
@karol-majewski
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment