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.
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.
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.
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.
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!
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...)
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.
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