编写通用的多维 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>>>

看起来不错!

Playground link to code