编写通用的多维 Map 类型
Write generic multi-dimensional Map type
我想创建一个通用的多维地图类型,其中 N 个类型中的最后一个是最终值,前面的类型实际上是一个键。
换句话说:
let m:MDMap<Coat,Pie,Date,Location>;
//...
m.get(myWoolCoat,myApplePie,yesterday) // returns a Location
如果没有可变参数泛型,虽然看起来我最终会得到这样的结果:
export type MultiMap<
A extends any,
B extends any,
C = void,
D = void,
// ... etc.
> = D extends void
? C extends void
? Map<A,B>
: Map<A,Map<B,C>>
: Map<A,Map<B,Map<C,D>>>
let m:MultiMap<Coat,Pie,Date,Location>;
我知道 Typescript 可以处理可变 元组 类型,但我无法将这种能力与类似上述愿望的东西联系起来。
我应该使用变通方法还是有什么方法可以实现某种递归泛型?
TypeScript 没有可变泛型,因此无法编写类似 MultiMap<...K, V>
的内容,这意味着存在任意数量的键类型参数和一个值类型参数。但是,正如您所注意到的,are tuple types 可以包含任意有序的类型列表,因此您可能对 MultiMap<K, V>
感到满意,其中 K
是一个元组。所以你应该写 MultiMap<[Coat, Pie, Date], Location>
而不是 MultiMap<Coat, Pie, Date, Location>
。反正就是多了几个字符。
至于让 MultiMap<[Coat, Pie, Date], Location>
解析为 Map<Coat, Map<Pie, Map<Date, Location>>>
,我们可以通过将 MultiMap<K, V>
定义为 recursive conditional type where we use variadic tuple types 来将 K
元组拆分为它的第一个元素 F
和元组的其余部分 R
:
type MultiMap<K extends any[], V> = K extends [infer F, ...infer R] ?
Map<F, MultiMap<R, V>> : V;
语法 K extends [infer F, ...infer R] ? XXX : YYY
被称为 conditional type inference,我们可以通过让编译器推断它们来将新类型参数引入范围。 K extends [infer F, ...infer R] ? XXX : YYY
意味着我们希望编译器尝试将 K
匹配到具有一些初始元素 F
和一些剩余元素 R
的可变元组。如果可行,那么在 XXX
类型表达式中,F
将是第一个元素类型,而 R
将是代表其余元素的较短元组。也就是说,如果 K
是 [Coat, Pie, Date]
,则 F
将是 Coat
,而 R
将是 [Pie, Date]
。因此 Map<F, MultiMap<R, V>>
将是 Map<Coat, MultiMap<[Pie, Date], V>>
并且您可以看到递归发生。如果 K
是一个空元组,那么我们根本没有键,我们只使用 V
,因此递归的基本情况终止了我们想要的方式。
让我们确保它有效:
type MyMap = MultiMap<[Coat, Pie, Date], Location>;
// type MyMap = Map<Coat, Map<Pie, Map<Date, Location>>>
看起来不错!
我想创建一个通用的多维地图类型,其中 N 个类型中的最后一个是最终值,前面的类型实际上是一个键。
换句话说:
let m:MDMap<Coat,Pie,Date,Location>;
//...
m.get(myWoolCoat,myApplePie,yesterday) // returns a Location
如果没有可变参数泛型,虽然看起来我最终会得到这样的结果:
export type MultiMap<
A extends any,
B extends any,
C = void,
D = void,
// ... etc.
> = D extends void
? C extends void
? Map<A,B>
: Map<A,Map<B,C>>
: Map<A,Map<B,Map<C,D>>>
let m:MultiMap<Coat,Pie,Date,Location>;
我知道 Typescript 可以处理可变 元组 类型,但我无法将这种能力与类似上述愿望的东西联系起来。
我应该使用变通方法还是有什么方法可以实现某种递归泛型?
TypeScript 没有可变泛型,因此无法编写类似 MultiMap<...K, V>
的内容,这意味着存在任意数量的键类型参数和一个值类型参数。但是,正如您所注意到的,are tuple types 可以包含任意有序的类型列表,因此您可能对 MultiMap<K, V>
感到满意,其中 K
是一个元组。所以你应该写 MultiMap<[Coat, Pie, Date], Location>
而不是 MultiMap<Coat, Pie, Date, Location>
。反正就是多了几个字符。
至于让 MultiMap<[Coat, Pie, Date], Location>
解析为 Map<Coat, Map<Pie, Map<Date, Location>>>
,我们可以通过将 MultiMap<K, V>
定义为 recursive conditional type where we use variadic tuple types 来将 K
元组拆分为它的第一个元素 F
和元组的其余部分 R
:
type MultiMap<K extends any[], V> = K extends [infer F, ...infer R] ?
Map<F, MultiMap<R, V>> : V;
语法 K extends [infer F, ...infer R] ? XXX : YYY
被称为 conditional type inference,我们可以通过让编译器推断它们来将新类型参数引入范围。 K extends [infer F, ...infer R] ? XXX : YYY
意味着我们希望编译器尝试将 K
匹配到具有一些初始元素 F
和一些剩余元素 R
的可变元组。如果可行,那么在 XXX
类型表达式中,F
将是第一个元素类型,而 R
将是代表其余元素的较短元组。也就是说,如果 K
是 [Coat, Pie, Date]
,则 F
将是 Coat
,而 R
将是 [Pie, Date]
。因此 Map<F, MultiMap<R, V>>
将是 Map<Coat, MultiMap<[Pie, Date], V>>
并且您可以看到递归发生。如果 K
是一个空元组,那么我们根本没有键,我们只使用 V
,因此递归的基本情况终止了我们想要的方式。
让我们确保它有效:
type MyMap = MultiMap<[Coat, Pie, Date], Location>;
// type MyMap = Map<Coat, Map<Pie, Map<Date, Location>>>
看起来不错!