4

我正在通过实现Fantasy Land Spec来探索 Typescript 类型系统,但在尝试实现Semigroup的规范时遇到了问题。

规范规定 aSemigroup应遵守以下类型定义:

concat :: Semigroup a => a ~> a -> a

我理解这意味着实现的类型a应该Semigroup有一个concat方法,该方法接受一个类型的参数a并返回一个类型的参数a

我能想到在 TypeScript 中表达这种类型定义的唯一方法是:

interface Semigroup {
    concat(other: this): this;
}

但是当我尝试在一个类上实现这个接口时,像这样:

class Sum implements Semigroup {
    constructor(readonly num: number) {}

    concat(other: Sum): Sum {
        return new Sum(this.num + other.num);
    }
}

我收到一个编译器错误,告诉我:

Property 'concat' in type 'Sum' is not assignable to the same property in base type 'Semigroup'.
  Type '(other: Sum) => Sum' is not assignable to type '(other: this) => this'.
    Type 'Sum' is not assignable to type 'this'.
      'Sum' is assignable to the constraint of type 'this', but 'this' could be instantiated with a different subtype of constraint 'Sum'.(2416)

感谢这个S/O 答案,我想我理解了这个问题。

我认为编译器本质上是在告诉我:你的接口说你应该接受一个具体类型的参数this(在这种特殊情况下),但也可以传入Sum一个扩展类。Sum

但是,我不知道如何解决它。也就是说,我不知道如何Semigroup在 TypeScript 中表达类型定义。如何从接口引用实现类?

这是TS Playground的链接。

更新

@Guerric P 的回答让我想到了部分解决方案。Guerric 的解决方案是在接口上使用泛型。此解决方案使实现规范成为Semigroup可能,如此处所示但接口并没有真正执行它。

幻想大陆进一步描述规范如下:

s.concat(b)

/** 
 * `b` must be a value of the same `Semigroup`
 *
 * If `b` is not the same semigroup, behaviour of `concat` is 
 * unspecified.
 *
 * `concat` must return a value of the same `Semigroup`.
 */

b认为我们至少可以将类型限制为Semigroup. 这样,它会强制执行b必须Semigroup为如下所示类型的约束:

interface Semigroup {
    concat(other: Semigroup): Semigroup;
}

但它仍然没有强制它必须是 SAME Semigroup。我仍在寻找一种使用 TypeScript 类型系统的方法。

4

1 回答 1

2

我不想质疑你对那个幻想土地规格的解释,我承认我并不完全理解,所以我假设你的解释是正确的。

问题是您class可以扩展,因此this可以引用该扩展类。TypeScript中没有类似的东西final class或等价物。

现在让我们假设你有一个ExtendedSum扩展类Sum。您的equals实现仍然有效,因为(other: Sum) => boolean可分配给(other: ExtendedSum) => boolean. 事实上,一个接受Sumas 参数的函数也可以接受一个ExtendedSum(结构类型原则)。

但是,您的concat实现不起作用,因为(other: Sum) => Sum不可分配给(other: ExtendedSum) => ExtendedSum. 实际上,返回 a 的函数Sum不能分配给返回 an 的函数,ExtendedSum因为 a不一定Sum是a 。ExtendedSum

您可以使用通用类型接口解决此问题:

interface Semigroup<T> {
    concat(other: T): T;
}

class Sum implements Setoid, Semigroup<Sum> {
    constructor(readonly num: number) {}

    equals(other: Sum): boolean {
        return this.num === other.num;
    }

    concat(other: Sum): Sum {
        return new Sum(this.num + other.num);
    }
}

TypeScript 游乐场

于 2022-01-06T18:13:05.293 回答