Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript array of different generic types

Tags:

typescript

If I have an array a where each element is an object consisting of two properties first and second, how should I declare `a's type such that the following is always satisfied?

 ForAll(x in a)(type(x.first) == T iff type(x.second) == (T => string))

I.e., I want to make sure that, for example, a[3].second(a[3].first) is typesafe.

like image 804
Hossam El-Deen Avatar asked Aug 13 '18 05:08

Hossam El-Deen


People also ask

How do you define an array of different generic types in TypeScript?

In typescript, an array is a data type that can store multiple values of different data types sequentially. Similar to JavaScript, Typescript supports array declaration and there are multiple ways to do it. Declaring and Initializing Arrays: We can either use var or let for declaring an array.

How do you declare an array of any type?

Arrays can be declared and initialized separately. let fruits: Array<string>; fruits = ['Apple', 'Orange', 'Banana']; let ids: Array<number>; ids = [23, 34, 100, 124, 44]; An array in TypeScript can contain elements of different data types using a generic array type syntax, as shown below.

How do I use generic types in TypeScript?

By passing in the type with the <number> code, you are explicitly letting TypeScript know that you want the generic type parameter T of the identity function to be of type number . This will enforce the number type as the argument and the return value.

How do I create an array of objects in TypeScript?

To declare an array of objects in TypeScript, set the type of the variable to {}[] , e.g. const arr: { name: string; age: number }[] = [] . Once the type is set, the array can only contain objects that conform to the specified type, otherwise the type checker throws an error. Copied!


2 Answers

The element type of the array would need to be an "existential type", which we could write in pseudocode as exists T. { first: T, second: (arg: T) => string }. TypeScript currently does not support existential types natively.

One potential workaround is to encode existential types using closures as explained in this answer. If you don't want to use real closures at runtime, you can use a utility library that provides a type definition for existential types (based on an encoding of type functions using indexed access types) and functions to produce and consume existential types that perform type casts but are just the identity at runtime:

// Library
// (Based in part on https://bitbucket.org/espalier-spreadsheet/espalier/src/b9fef3fd739d42cacd479e50f20cb4ab7078d534/src/lib/type-funcs.ts?at=master&fileviewer=file-view-default#type-funcs.ts-23
// with inspiration from https://github.com/gcanti/fp-ts/blob/master/HKT.md)

const INVARIANT_MARKER = Symbol();
type Invariant<T> = {
    [INVARIANT_MARKER](t: T): T
};

interface TypeFuncs<C, X> {}

const FUN_MARKER = Symbol();
type Fun<K extends keyof TypeFuncs<{}, {}>, C> = Invariant<[typeof FUN_MARKER, K, C]>;

const BAD_APP_MARKER = Symbol();
type BadApp<F, X> = Invariant<[typeof BAD_APP_MARKER, F, X]>;
type App<F, X> = [F] extends [Fun<infer K, infer C>] ? TypeFuncs<C, X>[K] : BadApp<F, X>;

const EX_MARKER = Symbol();
type Ex<F> = Invariant<[typeof EX_MARKER, F]>;
function makeEx<F, X>(val: App<F, X>): Ex<F> { 
    return <any>val;
}
function enterEx<F, R>(exVal: Ex<F>, cb: <X>(val: App<F, X>) => R): R { 
    return cb(<any>exVal);
}

// Use case

const F_FirstAndSecond = Symbol();
type F_FirstAndSecond = Fun<typeof F_FirstAndSecond, never>;
interface TypeFuncs<C, X> { 
    [F_FirstAndSecond]: { first: X, second: (arg: X) => string };
}

let myArray: Ex<F_FirstAndSecond>[];
myArray.push(makeEx<F_FirstAndSecond, number>({ first: 42, second: (x) => x.toString(10) }));
myArray.push(makeEx<F_FirstAndSecond, {x: string}>({ first: {x: "hi"}, second: (x) => x.x }));
for (let el of myArray) { 
    enterEx(el, (val) => console.log(val.second(val.first)));
}

(If there's enough interest, I may properly publish this library...)

like image 135
Matt McCutchen Avatar answered Nov 12 '22 13:11

Matt McCutchen


If you want an array where first will always have the same type, this will do

interface IArrayGeneric<T> {
    first: T;
    second: (arg: T) => string;
}

const a: Array<IArrayGeneric<   type   >>;

This will ensure that you can't put any object into a that doesn't satisfy the above requirements, but will also constrain T to one specific type you choose.

like image 43
EmandM Avatar answered Nov 12 '22 13:11

EmandM