对于遇到这种情况的人,TypeScript discord 服务器上有一个很好的示例:
export interface Hkt<I = unknown, O = unknown> {
[Hkt.isHkt]: never,
[Hkt.input]: I,
[Hkt.output]: O,
}
export declare namespace Hkt {
const isHkt: unique symbol
const input: unique symbol
const output: unique symbol
type Input<T extends Hkt<any, any>> =
T[typeof Hkt.input]
type Output<T extends Hkt<any, any>, I extends Input<T>> =
(T & { [input]: I })[typeof output]
interface Compose<O, A extends Hkt<any, O>, B extends Hkt<any, Input<A>>> extends Hkt<Input<B>, O>{
[output]: Output<A, Output<B, Input<this>>>,
}
interface Constant<T, I = unknown> extends Hkt<I, T> {}
}
可以如下使用。下面的sn-p定义了一个SetFactory,在这里你可以在创建工厂的时候指定想要的set类型,例如typeof FooSet 或 typeof BarSet 。 typeof FooSet 是 FooSet 的构造函数,类似于更高种类的类型,构造函数类型接受任何 T 并返回 FooSet<T>。 SetFactory 包含多个方法,例如 createNumberSet,它返回给定类型的新集合,类型参数设置为 number。
interface FooSetHkt extends Hkt<unknown, FooSet<any>> {
[Hkt.output]: FooSet<Hkt.Input<this>>
}
class FooSet<T> extends Set<T> {
foo() {}
static hkt: FooSetHkt;
}
interface BarSetHkt extends Hkt<unknown, BarSet<any>> {
[Hkt.output]: BarSet<Hkt.Input<this>>;
}
class BarSet<T> extends Set<T> {
bar() {}
static hkt: BarSetHkt;
}
class SetFactory<Cons extends {
new <T>(): Hkt.Output<Cons["hkt"], T>;
hkt: Hkt<unknown, Set<any>>;
}> {
constructor(private Ctr: Cons) {}
createNumberSet() { return new this.Ctr<number>(); }
createStringSet() { return new this.Ctr<string>(); }
}
// SetFactory<typeof FooSet>
const fooFactory = new SetFactory(FooSet);
// SetFactory<typeof BarSet>
const barFactory = new SetFactory(BarSet);
// FooSet<number>
fooFactory.createNumberSet();
// FooSet<string>
fooFactory.createStringSet();
// BarSet<number>
barFactory.createNumberSet();
// BarSet<string>
barFactory.createStringSet();
简要说明其工作原理(以FooSet 和number 为例):
- 主要要了解的类型是
Hkt.Output<Const["hkt"], T>。替换我们的示例类型后,它变为Hkt.Output<(typeof FooSet)["hkt"], number>。魔术现在涉及将其变成FooSet<number>
- 首先我们将
(typeof FooSet)["hkt"] 解析为FooSetHkt。通过将有关如何创建FooSet 的信息存储在FooSet 的静态hkt 属性中,很多魔法都在这里。您需要为每个受支持的类执行此操作。
- 现在我们有
Hkt.Output<FooSetHkt, number>。解析Hkt.Output 类型别名,我们得到(FooSetHkt & { [Hkt.input]: number })[typeof Hkt.output]。唯一符号 Hkt.input / Hkt.output 有助于创建唯一属性,但我们也可以使用唯一字符串常量。
- 现在我们需要访问
FooSetHkt 的Hkt.output 属性。这对于每个类都不同,并且包含有关如何使用类型参数构造具体类型的详细信息。 FooSetHkt 将输出属性定义为 FooSet<Hkt.Input<this>> 类型。
- 最后,
Hkt.Input<this> 只是访问了FooSetHkt 的Hkt.input 属性。它将解析为unknown,但通过使用交集FooSetHkt & { [Hkt.input]: number },我们可以将Hkt.input 属性更改为number。因此,如果我们达到了目标,Hkt.Input<this> 将解析为 number,FooSet<Hkt.Input<this>> 将解析为 FooSet<number>。
对于问题中的示例,Hkt.Output 本质上是所要求的,只是类型参数颠倒了:
interface List<T> {}
interface ListHkt extends Hkt<unknown, List<any>> {
[Hkt.output]: List<Hkt.Input<this>>
}
type HigherOrderTypeFn<T, M extends Hkt> = Hkt.Output<M, T>;
// Gives you List<number>
type X = HigherOrderTypeFn<number, ListHkt>;