【问题标题】:In TypeScript, how to reference the implementing class from an interface?在 TypeScript 中,如何从接口引用实现类?
【发布时间】:2022-01-06 17:09:48
【问题描述】:

我正在通过实现 Fantasy Land Spec 来探索 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)

感谢this S/O 的回答,我想我明白了这个问题。

我认为编译器本质上是在告诉我:您的界面表明您应该采用具体类型为 this 的参数(在这种特殊情况下为 Sum),但扩展 Sum 的类可以也可以传入。

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

这是 TS Playground 的link

更新

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

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

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 类型系统的方法。

【问题讨论】:

    标签: typescript interface concatenation this semigroup


    【解决方案1】:

    我不想质疑你对幻想大陆规范的解释,我承认我并不完全理解,所以我假设你的解释是正确的 ?。

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

    现在假设您有一个扩展SumExtendedSum 类。您的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 playground

    【讨论】:

    • 这将使 Semigroup 类型的正确实现成为可能,但不会强制执行其合同。有了这样的接口就可以做到class Sum implements Semigroup&lt;Date&gt; { concat(other: Date) } tsplay.dev/NVgpqm
    • 但这让我想到了一个稍微好一点但并不完美的解决方案。我们至少可以将T 的类型限制为Semigrouptsplay.dev/WoJEjm
    猜你喜欢
    • 1970-01-01
    • 2021-07-20
    • 1970-01-01
    • 2019-06-27
    • 2021-02-21
    • 1970-01-01
    • 2020-08-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多