Skip to content

Instantly share code, notes, and snippets.

@dedurus
Last active February 12, 2023 19:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dedurus/4418484e6272f7f0dd9ce9256bee569d to your computer and use it in GitHub Desktop.
Save dedurus/4418484e6272f7f0dd9ce9256bee569d to your computer and use it in GitHub Desktop.
Higher Order Type Level Functions
/**
* Generic helpers
*/
interface Fn {
input: unknown;
output: unknown;
}
type Call<fn extends Fn, input> = (fn & { input: input })["output"];
type Stringifiable = string | number | boolean | bigint | null | undefined;
/**
* Generic Array operations
*/
/**
* Reduce
*/
type Reduce<xs, acc, fn extends Fn> = xs extends [infer first, ...infer rest]
? Reduce<rest, Call<fn, { acc: acc; item: first }>, fn>
: acc;
/**
* Define Map in terms of Reduce
*/
interface MapFn<fn extends Fn> extends Fn {
output: this["input"] extends {
acc: infer acc extends any[];
item: infer item;
}
? [...acc, Call<fn, item>]
: never;
}
type ListMap<xs, fn extends Fn> = Reduce<xs, [], MapFn<fn>>;
/**
* Define Filter in terms of Reduce
*/
interface FilterFn<fn extends Fn> extends Fn {
output: this["input"] extends {
acc: infer acc extends any[];
item: infer item;
}
? Call<fn, item> extends true
? [...acc, item]
: acc
: never;
}
type Filter<xs, fn extends Fn> = Reduce<xs, [], FilterFn<fn>>;
/**
* Examples
*/
interface ToPhrase extends Fn {
output: `number is ${Extract<this["input"], string | number | boolean>}`;
}
type ys = ListMap<[1, 2, 3], ToPhrase>;
// ^? type ys = ["number is 1", "number is 2", "number is 3"]
type MakeRange<n, acc extends any[] = []> = acc["length"] extends n
? acc
: MakeRange<n, [...acc, acc["length"]]>;
type AddNumbers<a, b> = [...MakeRange<a>, ...MakeRange<b>]["length"];
type StringToNumber<str> = str extends `${infer n extends number}` ? n : never;
interface Add extends Fn {
output: this["input"] extends {
acc: infer acc;
item: infer item;
}
? AddNumbers<acc, item>
: never;
}
interface Join<sep extends string> extends Fn {
output: this["input"] extends {
acc: infer acc extends Stringifiable;
item: infer item extends Stringifiable;
}
? `${acc}${sep}${item}`
: never;
}
type sum = Reduce<[1, 2, 3, 4], 0, Join<"-">>;
// ^? type sum = "0-1-2-3-4"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment