为什么下面的 TypeScript 程序不会抛出类型错误?

Why doesn't the following TypeScript program throw a type error?

考虑 following program

interface Eq<A> {
  eq(this: A, that: A): boolean;
};

class Pair<A> implements Eq<Pair<A>> {
  constructor(public x: A, public y: A) {}

  eq(this: Pair<A>, that: Pair<A>): boolean {
    return this.x === that.x && this.y === that.y;
  }
}

class Triple<A> implements Eq<Triple<A>> {
  constructor(public x: A, public y: A, public z: A) {}

  eq(this: Triple<A>, that: Triple<A>): boolean {
    return this.x === that.x && this.y === that.y && this.z === that.z;
  }
}

const eq = <A extends Eq<A>>(x: A, y: A): boolean => x.eq(y);

console.log(eq(new Pair(1, 2), new Triple(1, 2, 3)));
console.log(eq(new Triple(1, 2, 3), new Pair(1, 2)));

我原以为 TypeScript 编译器会抱怨最后两行,因为您不应该将 eq 函数应用于两个不同类型的值。但是,TypeScript 编译器不会为上述程序抛出任何类型错误。上述程序的结果是 truefalse.

为什么 TypeScript 编译器不会为上述程序抛出类型错误?我们怎样才能让它正确捕获这些类型的错误?

由于 TypeScript 中使用了 structural subtyping,程序得以编译(与其他编程语言中经常出现的名义子类型相反)。

请注意,您的 Triple class 是一个可以分配给类型 Pair:

的变量
const p: Pair<number> = new Triple(1, 2, 3);

在你的例子中:

console.log(eq(new Pair(1, 2), new Triple(1, 2, 3)));
console.log(eq(new Triple(1, 2, 3), new Pair(1, 2)));

eq 的类型推断为:

const eq: <Pair<number>>(x: Pair<number>, y: Pair<number>) => boolean

如上所示,TriplePair 类型参数的有效参数,因此一切都可以干净地编译。

您可以将不同的私有字段添加到您的 classes 以模拟名义子类型化。在此特定示例中,您可以选择以下两个选项之一:

  • 添加额外的标记字段
  • xyz 设为私有并提供吸气剂

首先我投票给@VLAZ 的评论。

由于 Pair 和 Triple 都实现了 Eq,我相信这就是您没有编译错误的原因。

此外,根据@VLAZ 的评论,TS 不支持更高种类的类型。

既然你使用的是 F-bounded 多态性,我相信你应该给 TS 一个关于第二个参数的提示。

interface Eq<A> {
  eq(this: A, that: A): boolean;
};

class Pair<A> implements Eq<Pair<A>> {
  constructor(public x: A, public y: A) { }

  eq(this: Pair<A>, that: Pair<A>): boolean {
    return this.x === that.x && this.y === that.y;
  }
}

class Triple<A> implements Eq<Triple<A>> {
  constructor(public x: A, public y: A, public z: A) { }

  eq(this: Triple<A>, that: Triple<A>): boolean {
    return this.x === that.x && this.y === that.y && this.z === that.z;
  }
}

// hint is here :D
const eq = <Fst extends Eq<Fst>, Scd extends Eq<Scd> & Fst>(x: Fst, y: Scd): boolean => x.eq(y);

const x = eq(new Pair(1, 2), new Triple(1, 2, 3)); // ok
const y = eq(new Triple(1, 2, 3), new Pair(1, 2)); // error

这不是一个完整的答案,但我希望它能给你一些线索