Skip to content
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

Add documentation for interface declarations #833

Closed
GGAlanSmithee opened this issue Sep 21, 2015 · 24 comments
Closed

Add documentation for interface declarations #833

GGAlanSmithee opened this issue Sep 21, 2015 · 24 comments

Comments

@GGAlanSmithee
Copy link

I am using declarations to abstract away third party code, but also to losen the coupling to a specific implementation. However, I am experiencing a problem, which is that I am unable to make a class compatible with a declaration.

I have created a minimal repo where you can see the issue. If you look at src/index.js, there is a functionand a class that are equal to each other, and both are (should be) compatible with the declarationfound in lib/i-test.js.

Running flow on this code gives error This type is incompatible with for the class, but not for the function. I might be misstaken in the usage, but if so I can't see the incompatability.

PS. I am using flow-bin v0.16, to install dependencies run npm i

@GGAlanSmithee GGAlanSmithee changed the title "implelementing" declaration with class gives "This type is incompatible with.." error "implementing" declaration with class gives "This type is incompatible with.." error Sep 21, 2015
@GGAlanSmithee
Copy link
Author

Update:

I have found a solution to this problem, by declaring a type instead of declaring a class.
I have updated the repo with a new type declaration and a working example.

I would be greatful if someone would confirm that this is the expected behaviour and that it was a user error on my part before closing this issue. Thanks.

@GGAlanSmithee
Copy link
Author

I see that another solution to this is to use an interface instead.

I could not find any documentation for this though. Is the reason for this being undocumented that it is only supported because of TypeScript compatability?

@samwgoldman
Copy link
Member

Glad you found the solution — declaring a class also creates a runtime identifier, so that's definitely not what you wanted. interface is exactly what you want here, and FWIW you don't need to declare it in the libs; you can declare interfaces in your normal source files and use import type to get at them from other files.

I'm going to re-title this issue and leave it open as a prompt to add documentation for interfaces.

@samwgoldman samwgoldman changed the title "implementing" declaration with class gives "This type is incompatible with.." error Add documentation for interface declarations Oct 3, 2015
@GGAlanSmithee
Copy link
Author

Thanks for your answer @samwgoldman

I did try to have the interface with the rest of the code (the folders being watched by flow) but running flow then produced error Use of future reserved word in strict mode accompanied by a Required module not found from where I was trying to use the interface. Because of this, declaring it in the libs made the most sense to me, since flow is already aware of that folder.

BTW, just as a side note, it would be very nice to be able to do this:

interface ITest {
    test(val : number) : string;
}

class Test implements ITest {
    test(val : number) : string {
        return val.toString();
    }
}

And have flow varify that Test actually implements ITest. I guess implements is a "future reserved word" (if I'm not mistaken), but so is interface.

@henridf
Copy link
Contributor

henridf commented Dec 23, 2015

Pending the documentation, can someone explain the use of typeof in declaration files? I'm doing some work on the declarations in lib/node.js and can't figure out what the rationale (if any) is for some of the return types there using typeof and others not.

For example:
https://github.com/facebook/flow/blob/master/lib/node.js#L698 returns a net$Server
https://github.com/facebook/flow/blob/master/lib/node.js#L973 returns a typeof tls$Server

@samwgoldman
Copy link
Member

Given class C, the type C is inhabited by instances of C. In lib/node.js, the uses of typeof appear to be of the type typeof T where T is a class. This usage is equivalent (I believe—if there is a difference, please let me know) to Class<T> which is described in the docs.

@henridf
Copy link
Contributor

henridf commented Dec 23, 2015

the uses of typeof appear to be of the type typeof T where T is a class.

indeed they mostly are; there's at least one exception (net$Server) as noted above.

@zerkalica
Copy link

What is "implements" alternative for classes?
How to do in flow type this example without implements, converting classes to objects and boilerplate code?

type IUser = {
    id: string;
    relations: Array<IUser>;
}

class CustomUser implements IUser {
    id: string;
    relations: Array<CustomUser>;

    displayName: string;
}

function doSomethingWithCustomUser(user: CustomUser): void {
    // code
}

function doSomethingWithBaseUser(user: IUser): void {
    // do not knows about user.displayName
}

@samwgoldman
Copy link
Member

@zerkalica Thanks for providing that example! At the moment Flow doesn't support an implements assertion on the class declaration (it's a TODO for us), but we do support subtyping, so you can use IUser as an interface.

However, there is a subtle type error in your example code, which I'll try to explain.

// @flow

// note: changed type to interface
interface IUser {
    id: string;
    relations: Array<IUser>;
}

// note: no explicit `implements`
class CustomUser {
    id: string;
    relations: Array<CustomUser>;

    displayName: string;
}

function doSomethingWithCustomUser(user: CustomUser): void {
    // code
}

function doSomethingWithBaseUser(user: IUser): void {
    // do not knows about user.displayName
}

declare var customUser: CustomUser;
doSomethingWithBaseUser(customUser); // error
test.js:24
 24: doSomethingWithBaseUser(customUser);
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function call
  5:     relations: Array<IUser>;
                          ^^^^^ IUser. This type is incompatible with
 10:     relations: Array<CustomUser>;
                          ^^^^^^^^^^ CustomUser

Flow is complaining that the relations member array's element type is incompatible. This is because arrays are mutable. When I pass customUser into doSomethingWIthBaseUser, that method might insert a non-CustomUser instance into the relations array, which would be unsound.

Hopefully this type support can fix that issue as well, but when I tried I found a separate issue, which I'm looking into now.

Here's the TL;DR, though. We don't currently support an implements assertion in a class declaration, but you can declare interfaces and use subtyping today. We will implement implements in the future and you still need to watch out for mutability, which prevents sound subtyping in many instances.

Hope this helps!

@zerkalica
Copy link

@samwgoldman Thank you for the answer.

@STRML
Copy link
Contributor

STRML commented Jan 20, 2016

What's the status on documentation for this? interface and mixins are incredibly useful to get closer to the expressiveness of TS, but they are not documented anywhere.

Is there a way to submit update to the documentation site or a WIP?

@samwgoldman
Copy link
Member

flowtype.org is powered by GitHub pages, so to contribute you just need to send a PR to the gh-pages branch of this repo.

@vaukalak
Copy link

@samwgoldman I'd like to document this, but actually I don't understand at all, what is the point of interfaces in flow. Seems that they use is completely covered by types:

https://gist.github.com/vaukalak/64be636be0b09181eb31

@GGAlanSmithee
Copy link
Author

Don't know of what differences there are between an interface and a type in flow. However, I think one of the reasons interfaces exists is because of compatability with typescript.

@GGAlanSmithee
Copy link
Author

@vaukalak I see you commented in a related issue, som obviously you were already aware of what I stated above. Sorry for the noise.

ghost pushed a commit that referenced this issue Mar 31, 2016
Summary:Related to #833, adds note about mixins for declared classes and adds a note about similarity to TypeScript and Dart.
Closes #1337

Reviewed By: samwgoldman

Differential Revision: D3114147

Pulled By: gabelevi

fb-gh-sync-id: 4ac560e03864686005da0aaaf3dc1b0b694b2fb8
fbshipit-source-id: 4ac560e03864686005da0aaaf3dc1b0b694b2fb8
@jednano
Copy link

jednano commented Jan 17, 2017

@samwgoldman, you said support for implements is a TODO for you. Is there an issue number to track this? I'm very interested in the following syntax:

class Foo extends Bar implements Baz, Qux {}

@mhagmajer
Copy link

mhagmajer commented Jan 17, 2017 via email

@GGAlanSmithee
Copy link
Author

@mhagmajer implements is indeed a future reserved word (not in ES6 though) but personally, I would not mind flow using it. As long as they are willing to adjust to line up with any future implementation.

@jednano
Copy link

jednano commented Jan 17, 2017

I should have noted that i stole the syntax from TypeScript.

@dimpiax
Copy link

dimpiax commented Jan 20, 2017

What about: class A: B, C, D? Where B is a class,C and D are interfaces.

@jednano
Copy link

jednano commented Jan 20, 2017

@mhagmajer the problem with the (new Foo(): Baz) syntax is not only that you have to type that every time you're interested in using Baz props, but then you also lose type information about Foo. Consider the following example:

/* @flow */

class Foo {
  qux = 'corge';
  bar() {
    return 'baz';
  }
}

interface Baz {
  qux: string;
}

const x = (new Foo(): Baz);
x.qux; // OK
x.bar(); // Error: property 'bar' not found.

Flow and TypeScript both are interested in aligning with ES6 as much as possible, so I'm not sure how "intrusive" the implements syntax would be.

@dimpiax, I don't think a simple class A: B, C, D would work either, because you can only extend one class, but you can implement one or more interfaces. It's a bit misleading that you can just list them out like that w/o some kind of separation between the class you're extending and the interface(s) you're implementing.

To translate the "broken" Flow example above into TypeScript, you can see how it preserves both the class and the interface type information upon instantiation.

interface Baz {
  qux: string;
}

interface Thud {
  fred: number;
}

class Foo implements Baz, Thud {
  qux = 'corge';
  bar() {
    return 'baz';
  }
}

const x = new Foo();
x.bar(); // OK
x.qux; // OK
x.fred; // OK

@mhagmajer
Copy link

mhagmajer commented Jan 20, 2017 via email

@ckknight
Copy link
Contributor

One way to do this:

interface Baz {
  qux: string;
}

interface Thud {
  fred: number;
}

class Foo /* implements Baz, Thud */ {
  qux = 'corge';
  fred = 0;
}
/*:: ((({}: any): Foo): Baz); // Foo implements Baz */
/*:: ((({}: any): Foo): Thud); // Foo implements Thud */

This guarantees that Foo can be cast to Baz and Thud without issue, and without making guarantees about how to construct a Foo.

@samwgoldman
Copy link
Member

implements has existed for a while

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants