Reputation: 565
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
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