I need to add a type declaration to a function which maps elements of a tuple [Foo<A>, Foo<B>, ...]
to a function () => [A, B, ...]
. How can I achieve this in TypeScript?
This example is structurally similar to the relevant part of the application:
interface SomethingWithAValue<T> { value: T; }
function collect(array) {
return () => array.map(a => a.value);
}
This returns a tuple of the values associated with each object. What would the type declaration for collect
look like?
Pseudocode:
function collect<[SomethingWithAValue<T>...](array: [SomethingWithAValue<T>...]): () => [T...];
Update in response to jonrsharpe's suggestion:
interface SomethingWithAValue<T> { value: T; }
function collect<T>(array: SomethingWithAValue<T>[]): () => T[] {
return () => array.map(a => a.value);
}
type myTupleType = [string, number];
let somethings: [SomethingWithAValue<string>, SomethingWithAValue<number>];
somethings = [{ value: 'foo' }, { value: 5 }];
let fn: () => myTupleType = collect(somethings);
This does not work:
Argument of type '[SomethingWithAValue<string>, SomethingWithAValue<number>]' is not assignable to parameter of type 'SomethingWithAValue<string>[]'.
Types of property 'pop' are incompatible.
Type '() => SomethingWithAValue<string> | SomethingWithAValue<number>' is not assignable to type '() => SomethingWithAValue<string>'.
Type 'SomethingWithAValue<string> | SomethingWithAValue<number>' is not assignable to type 'SomethingWithAValue<string>'.
Type 'SomethingWithAValue<number>' is not assignable to type 'SomethingWithAValue<string>'.
Type 'number' is not assignable to type 'string'.
let stud3 = Student[2]; In this tuple destructuring we simply manually break the value of tuple and assign it to variable. In destructuring, we basically assign the value of each element of tuple to some variable. This is the basic example of destructuring of a tuple.
A tuple is a typed array with a pre-defined length and types for each index. Tuples are great because they allow each element in the array to be a known type of value.
Tuples are mutable which means you can update or change the values of tuple elements.
Typically an array contains zero to many objects of a single type. TypeScript has special analysis around arrays which contain multiple types, and where the order in which they are indexed is important. These are called tuples. Think of them as a way to connect some data, but with less syntax than keyed objects.
UPDATE: The answer below is obsolete as of TS3.1. I believe you can now use mapped tuple types and conditional type inference to get the behavior you want:
type ExtractValue<T extends ReadonlyArray<SomethingWithAValue<any>>> =
{ [K in keyof T]: T[K] extends SomethingWithAValue<infer V> ? V : never };
function collect<T extends ReadonlyArray<SomethingWithAValue<any>>>(
array: T
): () => ExtractValue<T> {
return () => array.map(a => a.value) as any;
}
And let's use it... First let's make it easy to get a tuple type with a helper function tuple()
which takes in a variadic number of arguments and outputs a tuple (this was made possible as of TS3.0)
type Narrowable = string | number | boolean | undefined | null | void | {};
const tuple = <T extends Narrowable[]>(...t: T) => t;
And let's see if it works:
const result = collect(tuple({ value: 10 }, { value: "hey" }, { value: true }));
// const result: () => [number, string, boolean]
Looks good!
OLD ANSWER:
There are no variadic kinds in TypeScript, so there's no way to type a generic tuple (nothing like the syntax [T...]
exists).
As a workaround, you can provide function overloads for tuples of any length up to some reasonable maximum:
function collect<A, B, C, D, E>(array: [SomethingWithAValue<A>, SomethingWithAValue<B>, SomethingWithAValue<C>, SomethingWithAValue<D>, SomethingWithAValue<E>]): () => [A, B, C, D, E];
function collect<A, B, C, D>(array: [SomethingWithAValue<A>, SomethingWithAValue<B>, SomethingWithAValue<C>, SomethingWithAValue<D>]): () => [A, B, C, D];
function collect<A, B, C>(array: [SomethingWithAValue<A>, SomethingWithAValue<B>, SomethingWithAValue<C>]): () => [A, B, C];
function collect<A, B>(array: [SomethingWithAValue<A>, SomethingWithAValue<B>]): () => [A, B];
function collect<A>(array: [SomethingWithAValue<A>]): () => [A];
function collect<T>(array: SomethingWithAValue<T>[]): () => T[] {
// implementation
}
That should work for tuples up to length 5, and you can add other overloads at the top to get up to whatever you need in practice. It's verbose and ugly but it should work.
Hope that helps; good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With