ウェブサイト検索

異なるジェネリック型のTypeScript配列


もし私が各要素が first と second の2つのプロパティを持つオブジェクトで構成される配列 a を持っている場合、次の条件が常に満たされるように a の型をどのように宣言すべきですか?

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

つまり、例えば a[3].second(a[3].first) が型安全であることを確認したいということです。

回答 #1

配列の要素型は「存在型」である必要があり、擬似コードでは exists T. { first: T, second: (arg: T) => string } と書くことができます。TypeScript は現在、存在型をネイティブでサポートしていません。

潜在的な回避策のひとつは、この回答で説明されているように、クロージャを使って存在型をエンコードすることです。もしランタイムで実際のクロージャを使いたくない場合は、存在型の型定義(インデックスアクセス型を使った型関数のエンコードに基づく)と、型キャストを行うがランタイムでは単に識別子として扱われる存在型を生成および消費する関数を提供するユーティリティライブラリを使うことができます:

// 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)));
}

(もし十分な関心があれば、このライブラリを正式に公開するかもしれません…)

回答 #2

もし最初の要素が常に同じ型である配列が欲しいなら、これはそれに適しています

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

const a: Array<IArrayGeneric<   type   >>;

これにより、上記の要件を満たさないオブジェクトを a に入れることはできなくなりますが、同時に T を選択した特定の type に制約することもできます。

関連記事