-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TypeScript interface support #72
Comments
We'll (have to) have at least equivalent functionality, since we're going to try converting .d.ts files to Flow declaration files. That said, TypeScript interface syntax is quite fat, trying to kill too many birds with one stone. Their functionality can be decomposed into (a) type aliases (b) declare classes, both of which we have, and will augment however is needed to model different kinds of declarations elegantly. |
Can you elaborate on this "That said, TypeScript interface syntax is quite fat" |
I mean TypeScript interfaces model, at the same time, objects, function-like things, constructors, classes, array-like things, ...by "syntax is quite fat" I mean "trying to do too many things at once, thus too many productions from that non-terminal". In our experience, these features can be nicely factored out. Functions should be functions, arrays should be arrays, objects should be objects (yes, everything is internally an object, but the type language doesn't need to model that). What remains are what are internally things that behave like classes (by being constructors, holder of instance properties, holder of static properties, holder of a prototype, holder of an indexable, and so on...). For that, we have declare class. So basically we're trying to limit declare class to class-like things, which can be sufficiently general, while not polluting the syntax of all object types with that generality. |
For those of you who ended up here while searching for "Facebook Flow interfaces," perhaps a simple example would be good:
Hope that helps, and, I hope that my example isn't too stupid. Erin |
+1 to this. It would really help to have type interfaces for existing libraries like lodash, underscorejs, angular, ember or whatever. Right now getting started with an existing codebase mostly requires writing long interface files. Because of that adopting flowtype might be more work than it avoids by finding bugs earlier. Of course making proper flow interfaces from .d.ts files would require some manual work because typescript doesn't support non-nullable types. Therefore a conversion to a flow interface would allow it to be updated manually to properly fit FlowType. |
Merging with #7 |
I am sorry to write it here, but I have no idea where to write it elsewhere. It seems to me flow now has an Is it documented anywhere? Google returns me either my own issue #544, or things about the interface files, but that's not what I want. I want to know what edit: some limited test seems to be here https://github.com/facebook/flow/blob/master/tests/traits/test2.js but, well, it's not that helpful :D also I have no idea what |
OK, I think I slightly understand what |
@Runn1ng if you do, please explain what is advantage of interfaces compared to types: https://gist.github.com/vaukalak/64be636be0b09181eb31 |
@vaukalak in general the main difference between interface and type in languages without multiple inheritance is that a class can be a subtype of only one type, but can implement multiple interfaces. |
@amakhrov actually you can implicitly implement a type, and as I understand, there is no difference in how many types you do implement. the extension can be used in classes only. |
The absence (afaict) of available interface files for third-party projects makes flow a non-starter for me; I primarily need it for enforcing correctness in external interfaces. I suspect I'm far from alone. So if there's any way to make .d.ts work, maybe it would enable a lot more Flow adoption. (I'm not disagreeing with the technical issues.) |
+1 to @estaub ... I began down the path of implementing FlowType on my work project BUT barrier after barrier and then this issue with interfaces and lack of support of d.ts was the last straw .. p.s. this idea that TS's implementation is "Fat" is moot in my opinion, given its all compile time anyway.. |
@liquidboy From a structural typing point there is not really much of a difference between an interface or an aliassed (named) object type. There are some obvious syntactic differences though: Defining methods in interfaces is arguable easier than function types in object types. It certainly allows for easier converting of TypeScript declarations. You can also "extend" interfaces: interface A { interface B extends A { var b : B = { foo() {}, bar() {}} To do the same with object types would need a union type. |
For those who came here by searching for "interfaces in flowtype", let me elaborate on laser's great comment above (thanks laser, you helped me out with it!). If you want to have a class that (sort of) implements a (sort of) interface, you can do the following:
The comment in the middle is a hack that does the same as what you would expect Technically, this is a Flow Comment (one that Flow parses but the compiler ignores; make sure you have the double colon at the front). In the flow comment there is an expression that evaluates to an arrow function, with an argument of type UserDAOImpl, and in the body a Flow typecheck checking that x is of type UserDAOIface. Conceptually this means that all variables of type UserDAOImpl must be of type UserDAOIface. You can read the expression as "x is UserDAOImpl implies that x is UserDAOIface", abusing the fact that You can even use the same expression without putting it into a comment (however, I personally prefer it with the comment):
Or you can equally put it at the end:
|
@neonsquare, unfortunately you can't union or intersect exact object types together. In TypeScript, all interfaces are exact by default. Flow is backwards if you ask me, because now I'm forced to put pipes in 99% of my types (i.e., Let me give you a quick and dirty example of a simple type Point = {|
x: number,
y: number
|}; That works out pretty well, until you want to extend it in any way whatsoever (#2626). Coming from TypeScript, I'm finding myself extremely limited by what Flow has to offer. Let's take the interface Point {
x: number;
y: number;
}
interface Point3D extends Point {
z: number;
}
interface Named {
name: string;
}
interface NamedPoint extends Named, Point { }
interface NamedPoint3D extends Named, Point3D { }
const point: NamedPoint3D = {
name: 'target',
x: 50,
y: 100,
z: -25
}; Try doing the above in Flow w/o pulling your hair out, because it's not even possible (#2626). Even #1326 wouldn't make me happy, because I expect to see errors when you extend/intersect incompatible interfaces/types. Fortunately, in TypeScript, you can mimic Flow's default behavior with Excess Property Checks. Here's the syntax: interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any; // <-- exact type becomes flexible (the Flow default)
} Unfortunately, in Flow, you can't mimic TypeScript behavior in reverse. I should probably create a host of issues here on Flow, but from the existing issues I've read, I'm wondering if it's just not in the design goals of Flow to meet these requirements. Edit: Just did 👇 |
How do I upvote @jedmao's comment with the power of a million votes. |
That is not true, interfaces has the same semantics in TypeScript. |
@vkurchatkin, my statement holds true. In TypeScript (not Flow), all interfaces are exact by default (i.e., they do not accept excess properties). I realize Flow has interface declarations; though, the implementation is immature. To support that statement, the documentation contains only 46 words to describe what it can do, which leads me to believe it can only do that one thing. Anything else is undocumented. In contrast, TypeScript's interface documentation has 2825 words in it. That's 61 times the documentation that Flow has! Unfortunately, Flow's interfaces also don't do enough to solve the problems I've stated above (I checked), so until the interface support is more mature, I can't really compare it to a TypeScript interface, so that's not what I'm talking about at all. If I had to translate Flow's object type into TypeScript today, I would write an interface. There is no 1:1 relationship, but that's what I would write. In other words, TypeScript's counterpart to a Flow object type is an interface. This is based on functionality, not grammar. Flow's counterpart to a TypeScript interface, however, would be more accurately described as the "exact" object type, as it uses Excess Property Checks to prevent someone from attempting to supply an unsupported property, because "it's probably an error." Clear as mud? |
No, interfaces in TypeScript ar not exact. "Exact" means that if value has type T, it is guaranteed to not have any properties than those of T. There is no way to achieve this in TypeScript. What you are talking about is that in some contexts TypeScript shows linter-level errors that has nothing to do with type system. There are no linter-level errors in Flow yet. |
As a user, why do I care "how" a type or interface guarantees exactness? And how would a type system guarantee anything without throwing errors? Call it what you want. If I have a TypeScript interface, TypeScript guarantees exactness via linter-level errors, does it not? What do you mean "in some contexts"? In my experience, every context of an interface behaves this way. |
No it doesn't. interface Point {
x: number;
y: number;
};
const a: { x: number; y: number; z: number; } = { x: 5, y: 5, z: 5 };
const b: Point = a; In this example This is an error in TS indeed: interface Point {
x: number;
y: number;
};
const a: Point = { x: 5, y: 5, z: 5 }; TypeScript prints the following:
That is obviously incorrect, as in the first example we did just that. Actually, TypeScript is probably right that this is probably a mistake. The only reference to this object doesn't know about |
@vkurchatkin thanks for the concrete example. You are absolutely correct about TypeScript "not" guaranteeing exactness in your example; however, it can be fixed like so: const a = <Point>{ x: 5, y: 5, z: 5 }; It's a bit confusing that TS doesn't treat That said, can you explain to me how anything can be guaranteed in either type system? Isn't throwing errors the only way to guarantee anything? You keep pointing out that linter-level errors have nothing to do with a type system, but why should I care about the mechanism behind the error that guarantees exactness? |
What I'm saying is that you shouldn't call this "exactness". It's not the same thing. It's a valid feature request, but making objects exact by default is not what you want. |
I think in the context of Flow, exactness "is" what I want, because I want to intersect exact object types into a new exact object type, same as #2626. In the context of TypeScript, I also prefer exactness, so that would be a separate feature request, but it's not currently how things work. |
Hey, I just wanna add some perspective here. I'm not on the flow team nor am I a type system expert (very far from it). Please don't use what I say as an authority to apply pressure on the flow team. That wasn't my intention and I'm sure they have reasons for their design decisions :) |
Flow seems to have a limited interface support, no implements (and not supported in declaration files).
Would mimicking typescript interface support seems reasonable ?
The text was updated successfully, but these errors were encountered: