I may be completely misunderstanding -- but it seems like interfaces require you always type everything multiple times -- so why would i really care?
interface Foo {
channel: number
}
class Bar implements Foo {
constructor() {
this.channel = 2
}
}
This fails because "channel" is not typed on Foo even though it is implementing the inteface Foo which does define it... so instead I am forced to type it yet again
7: class Bar implements Foo {
^ Bar. This type is incompatible with
7: class Bar implements Foo {
^ Foo
Property `channel` is incompatible:
7: class Bar implements Foo {
^ property `channel` of Foo. Property not found in
7: class Bar implements Foo {
^ Bar
9: this.channel = 2
^ property `channel`. Property not found in
9: this.channel = 2
^ Bar
interface Foo {
channel: number
}
class Bar implements Foo {
channel: number;
constructor() {
this.channel = 2
}
}
Seems like this is completely redundant and it shoudl know channel is number based upon its interface. Then I should optionally be able to provide a type to Bar and if it conflicts with the interface it could warn me / throw an error ... additionally I would know because if i tried to set channel to 'hello world' in this case then it would tell me it is expecting a string?
So instead an interface just requires you do twice the work for the same benefit of just retyping the class 100% for each class...
I would also point out that even making it optional is an error so there is definitely no way around typing every single thing twice.
interface Foo {
channel?: number
}
class Bar implements Foo {
constructor() {
this.channel = 2
}
}
9: this.channel = 2
^ property `channel`. Property not found in
9: this.channel = 2
^ Bar
appears putting constructor in interface silently breaks in some places... constructors are important... too many flow bugs and issues - it takes more than 75% of the time at this point just trying to find ways to make flow accept things... just not worth it at all...
flow out --> typescript in. wish i would have just used ts from the start :(
For the why of typing channel a second time, see src/typing/class_sig.ml#L568-L573. More generally, interfaces have many uses. If they are pointless within an architecture, then they've been misused in that architecture.
On constructors within interfaces, I would argue that including them in interfaces is probably a mistake for a couple of reasons:
1) A constructor implies that an instance's constructor, which is accessible as the constructor property, is newable. Reconciling that with, say, objects with a callable constructor property fails conceptually.
2) Flow's classes admit constructors that do not subtype a corresponding superclass' constructor. By avoiding constructors in interfaces (and thereby eliminating constructor as a property), flaws of Flow's classes cannot poison interfaces.
By the way, the purpose of implements is to save Flow the trouble of checking structural subtyping at every position where an instance flows to an interface. I suppose there's also some value in checking subtyping at the implementation site instead of downstream where errors may be more ambiguous.
Interfaces are not designed to be a replacement of types... they are designed to be... an interface.
interfaces do not generate code, they define an expectation.
One may do
class Foo {
b: number = 1;
}
interface SomeBehavior {
a: number;
b: number;
}
function foo(bar: SomeBehavior) {
}
Say this is an exported library, and you INTENDED for Foo to always exhibit SomeBehavior, but forgot to add that a property.
Flow's not going to complain. No usages of Foo is being passed to foo.
Now if you added the implements SomeBehavior, Flow would of told you that you were missing part of the contract to qualify for SomeBehavior.
You can then type your API's to interfaces.
Sure your going to redefine that type on the class, but theres nothing wrong with that.
Look at it another way
interface Foo {
bar: number | string;
}
class Bar implements Foo {
bar: number;
}
class Baz implements Foo {
bar: string;
}
You are confirming to Flow that this class meets the specifications of that interface, and to throw an error if you ever fail to meet that spec.
Thanks for the quality answers to this question, @popham and @aikar
Most helpful comment
Interfaces are not designed to be a replacement of types... they are designed to be... an interface.
interfaces do not generate code, they define an expectation.
One may do
Say this is an exported library, and you INTENDED for Foo to always exhibit SomeBehavior, but forgot to add that a property.
Flow's not going to complain. No usages of Foo is being passed to foo.
Now if you added the implements SomeBehavior, Flow would of told you that you were missing part of the contract to qualify for SomeBehavior.
You can then type your API's to interfaces.
Sure your going to redefine that type on the class, but theres nothing wrong with that.
Look at it another way
You are confirming to Flow that this class meets the specifications of that interface, and to throw an error if you ever fail to meet that spec.