jmindel
jmindel

Reputation: 565

Recognizing Polymorphism Between Interfaces and Classes in Typescript

I’m currently working on a project in TypeScript (version 2.9.2), and have encountered an unexpected polymorphic behavior. In the likes of Java and C#, interfaces define polymorphic behavior as much as classes do—that is, in the following, it is just as true that item1 can be of type A as it is that it can be of type B, and that item2 can be of type C as it is that it can be of type D:

interface A { }
class B implements A { }
class C { }
class D extends C { }

But in TypeScript, this seems to not be the case. I have approximately the following setup:

interface A {
    new (str: string): Module;
    someFunction(str: string): A;
}

class B implements A {
    constructor(str: string) { /* ... */ }
    someFunction(str: string): B { /* ... */ }
}

The compiler seems to have an issue with the return type of B’s someFunction(), but by my understanding of polymorphism, since B implements A, if a function returns something of type A, then it should also be able to return something of type B. That being said, it doesn’t make sense that something should “be of type A”, since interfaces can’t be instantiated, and are no more than an intangible agreement or contract between classes. It then seems reasonable that if A were to instead be an abstract class, the polymorphic behavior should behave as I expect—which it indeed does—but in the scope of the library I’m building, it seems more proper for A to be an interface.

The issue that the compiler gives in particular is as follows on the line that declares B's someFunction():

[ts]
Property 'someFunction' in type 'B' is not assignable to the same property in base type 'A'.
  Type '(str: string) => B' is not assignable to type '(str: string) => A'.
    Type 'B' is not assignable to type 'A'.
      Types of property 'someFunction' are incompatible.
        Type '(str: string) => B' is not assignable to type '(str: string) => A'.
(method) Project.B.someFunction(str: string): B

Part of the problem seems to lie in the fact that I declare a constructor in A. If I delete that constructor definition, the problem is resolved, but I need that definition to be part of the agreement of what it fundamentally means to be of type A.

Given the polymorphic behavior I expect, how might I go about writing my interface, or should I be using an abstract class instead? How do I bring this polymorphic behavior about?

Upvotes: 3

Views: 1011

Answers (1)

artem
artem

Reputation: 51619

I need that definition to be part of the agreement of what it fundamentally means to be of type A

Unfortunately there is no support for that in the language. Construct signature can not be a part of contract that a class is declared to implement. extends declares only the instance part of the contract, constructors and static methods are part of so-called "static part" and there is no way to declare a contract for that.

TypeScript uses structural typing so you can in fact use B whenever an interface that specifies a construct signature is expected, but that interface must be declared separately, and the conformance will be checked every time B is used in-place, there is no way to declare it beforehand:

interface AConstructor {
    new (str: string): A;
}

interface A {
    someFunction(str: string): A;
}

class B implements A {
    constructor(str: string) { /* ... */ }
    someFunction(str: string): B { /* ... */ }
}

function f(cls: AConstructor) {
    const instance = new cls('hi');
    instance.someFunction('how are you?');
}

f(B);  // ok

Upvotes: 1

Related Questions