-
-
Notifications
You must be signed in to change notification settings - Fork 616
Open
Labels
enhancementNew feature or requestNew feature or request
Description
Hi! @sindresorhus I’d like to propose adding some types that simulate Array.map
and Array.reduce
behavior on tuple types in a reusable, higher-kinded style.
These utilities use a callback interface approach to allow extensible behavior via type “callbacks,” which makes it possible to create mapped/reduced outputs based on keys from a reusable callback map.
Down here are some basic implementation.
Example 1: ArrayMap
type ArrayMap<
Array_ extends UnknownArray,
Mapper extends MapCallBacks,
Array__ extends UnknownArray = Array_,
Index extends number = 0,
Acc extends UnknownArray = []
> =
Array_ extends readonly [infer Head, ...infer Tail]
? ArrayMap<Tail, Mapper, Array__, Increment<Index>, [...Acc,
MapCallBack<Head, Index, Array__>[Mapper]
]>
: Acc
;
Then users can define:
interface MapCallBack<V, I, T> {
'curr->next': Increment<I> extends infer Next extends number
? T[Next] extends string
? `${V & string}->${T[Next]}`
: never
: never;
'A + B': V extends readonly [infer A extends number, infer B extends number]
? Sum<A, B>
: never;
Snake: `__${V & string}__`;
Kebab: `--${V & string}--`;
}
And use like:
const arr1 = ['foo', 'bar', 'baz'] as const
const fn1 = arr1.map(x => `__${x}__` ) as ArrayMap<typeof arr1, 'Snake'>
// ^? ["__foo__", "__bar__", "__baz__"]
const arr2 = [[1, 2], [5, 10]] as const
const fn2 = arr2.map(([a, b]) => a + b) as ArrayMap<typeof arr2, 'A + B'>
// ^? [3, 15]
type M = ArrayMap<typeof arr1, 'curr->next'>
// ^? ["foo->bar", "bar->baz", never]
Example 2: ArrayReduce
type ArrayReduce<
Array_ extends UnknownArray,
Reducer extends ReduceCallBacks,
Array__ extends UnknownArray = Array_,
Index extends number = 0,
Acc = never
> =
Array_ extends readonly [infer Head, ...infer Tail]
? ArrayReduce<Tail, Reducer, Array__, Increment<Index>,
ReduceCallBack<Acc, Head, Index, Array__>[Reducer]
>
: Acc
;
Then users can define:
interface ReduceCallBack<P, V, I, T> {
Join: [P] extends [never] ? V : `${P & string}, ${V & string}`;
Max: [P] extends [never] ? V
: GreaterThanOrEqual<P, V > extends true ? P : V
Min: [P] extends [never] ? V
: LessThanOrEqual<P, V > extends true ? P : V;
}
And use like:
type TupleMax<T extends readonly number[]> = ArrayReduce<T, 'Max'>
const arr3 = [1, 22, 8, 10, 5, 4] as const
const fn3 = arr3.reduce((x, y) => x >= y ? x : y) as TupleMax<typeof arr3>
// ^? 22
const fn4 = arr3.reduce((x, y) => x <= y ? x : y) as ArrayReduce<typeof arr3, 'Min'>
// ^? 1
type R = ArrayReduce<['foo', 'bar', 'baz'], 'Join'>
// ^? 'foo, bar, baz'
Motivation
- This opens the door to abstracted, composable operations over tuple types without hardcoding logic into every single type.
- Users could define their own
MapCallBack
/ReduceCallBack
sets and re-use theArrayMap
/ArrayReduce
types.
Would love to hear your thoughts! I can open a PR if this is something you'd consider.
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request