我正在通过实现Fantasy Land规范来探索Typescript类型系统,在尝试为Semigroup实现规范时遇到了一个问题。
规范规定Semigroup
应遵守以下类型定义:
concat :: Semigroup a => a ~> a -> a
我理解这意味着实现Semigroup
的类型a
应该有一个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
的类
但是,我不知道如何修复它。也就是说,我不清楚如何在TypeScript中表达Semigroup
的类型定义。如何从接口引用实现类?
这是TS游乐场的链接。
更新
@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`.
*/
我认为我们至少可以将类型限制为Semigroup
,而不是将b
设为泛型。通过这种方式,它强制执行b
必须是Semigroup
类型的约束,如下所示:
interface Semigroup {
concat(other: Semigroup): Semigroup;
}
但它仍然没有强制要求它必须是相同的Semigroup
。我仍在寻找一种使用TypeScript类型系统的方法。
我不想质疑你对幻想土地规范的解释,我承认我不完全理解,所以我认为你的解释是正确的😉.
问题是class
可能被扩展,所以this
可能引用那个扩展类。TypeScript中没有final class
或等效内容。
现在让我们假设您有一个ExtendedSum
类,它扩展了Sum
。您的equals
实现仍然有效,因为(other: Sum) => boolean
可分配给(other: ExtendedSum) => boolean
。事实上,以Sum
为参数的函数也可以采用ExtendedSum
(结构类型原则(。
但是,您的concat
实现不起作用,因为(other: Sum) => Sum
不可分配给(other: ExtendedSum) => ExtendedSum
。实际上,返回Sum
的函数不可分配给返回ExtendedSum
的函数,因为Sum
不一定是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游乐场