从具有泛型参数的子类方法调用构造函数

Calling constructor from a subclass method with a generic parameter

我无法获取以下代码以进行类型检查:

type MyFunctionConstructor<T, F extends MyFunction<T>> = new (
  f: (n: number) => T
) => F;

class MyFunction<T> {
  constructor(f: (n: number) => T) {
    this.f = f;
  }

  f: (n: number) => T;

  composeT(g: (t: T) => T) {
    return new (this.constructor as MyFunctionConstructor<T, this>)(n =>
      g(this.f(n))
    );
  }

  composeU<U>(g: (t: T) => U) {
    return new (this.constructor as MyFunctionConstructor<U, this>)(n =>
      g(this.f(n)) // tsc error here, see below
    );
  }
}

class MyFancyFunction<T> extends MyFunction<T> {}

我收到以下错误:

Type 'this' does not satisfy the constraint 'MyFunction<U>'.
Type 'MyFunction<T>' is not assignable to type 'MyFunction<U>'.
Type 'T' is not assignable to type 'U'.

我不想按名称调用构造函数(即 new MyFunction(...)),因此如果 fMyFunction 的子类的实例(例如 FancyFunction) 那么 f.composeT(g)f.composeU(g) 也是如此。 composeT 中用于构造函数调用的 as 转换不适用于具有通用参数的更通用的 composeU 方法。我该如何处理额外的泛型,U?

(进行composeT类型检查的方法来自this answer。这个问题本质上是一个后续问题,我无法发表评论。)

正如我在评论中提到的,这在 TypeScript 的类型系统中无法真正表达(无论如何从 TS3.1 开始)。 TypeScript 表示所谓的 higher-kinded types 的能力非常有限。

首先,您想说 MyFunction<T> 的所有子类本身在 T 中必须是通用的。也就是说,您不想扩展 type MyFunction<T>,而是扩展 type constructor T ⇒ MyFunction<T>, which converts a type T into a MyFunction<T>. But you can't do that, because there's no general way to refer to type constructors in TypeScript (Microsoft/TypeScript#1213).

接下来,假设您可以扩展 T ⇒ MyFunction<T> 而不是 MyFunction<T>,您将需要 TypeScript 的 polymorphic this to respect that, so that this is also a type constructor and this<X> is a concrete type. And you can't do that either (Microsoft/TypeScript#5845).

由于 Microsoft/TypeScript#1213 仍然是一个悬而未决的问题并且处于 "help wanted" 状态,因此您最终有希望能够做到这一点。但我不会屏住呼吸。如果您查看该问题,您会看到一些人使用的解决方法,但在我看来,它们太麻烦了,我无法推荐。

您可以尝试类似的方法:

  composeU<U>(g: (t: T) => U): MyFunction<U> {
    return new (this.constructor as any)((n: number) =>
      g(this.f(n)); 
    );
  }

但是如果你想捕捉多态的精神,你需要明确地缩小每个子类的定义 this:

class MyFancyFunction<T> extends MyFunction<T> { }
interface MyFancyFunction<T> {
  composeU<U>(g: (t: T) => U): MyFancyFunction<U>;
}

上面我们用declaration merging缩小了MyFancyFunctioncomposeU方法。

总之,希望对您有所帮助。祝你好运!