-
Notifications
You must be signed in to change notification settings - Fork 24
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
Class inheritance #25
Comments
I'd like to start researching this. This will certainly be the deepest I've ever gone into the module/object system! |
@rrdelaney I have some plans I think I can implement this week, but I thought I'd get your feelings on approach. You may already have seen the Discord convo a couple days ago, but it boiled down to implementing inheritance via denormalization. So, for each type from which a type inherits, you essentially reprint the non-overridden parts of the inherited types, for each inheriting type. I haven't fully tested it, but rough examples printed at the bottom. I can think of two reasons not to do this:
An alternate option would be "all classes are open Js.t objects, so inheritance is just a type argument". I think this is technically not true in Flow, and it could conceivably mean that a BS-generated object could have extra members that somehow trigger unwanted behavior; maybe the underlying JS checks for some key Example of the first approach, in case my description had any ambiguities: JS:
RE:
|
@bbqbaron I saw the discussion on Discord - super through 👍 I don't know if this implementation would work though because OCaml uses nominal typing, but matching against structure. For example the following code causes a compile error in BuckleScript: type class1 = Js.t {. x: float };
type class2 = Js.t {. x: float, y: string };
external print_class : class1 => unit = "console.log" [@@bs.val];
let class1_instance: class1 = [%bs.raw "{ x: 1 }"];
let class2_instance: class2 = [%bs.raw "{ x: 1, y: 'hello' }"];
print_class class1_instance;
print_class class2_instance; Although declare module 'test' {
declare class Base { x: number };
declare class Sub extends Base { y: string };
declare function print(b: Base): string
} Although in Flow we could pass an instance of Is there anyway around that? Otherwise I don't have any problem generating a super-long definition per type. |
I think it's ok if we delay this feature a bit, we should start throwing a compile error when a class extends another though. |
Sure, no problem; I can add the failures quite soon. I was also mulling over the larger issue a little. To answer your question, I think the brute-force solution is simply to duplicate declarations for all known possible cases. So, in your example,
(ignoring for a moment whether we care about mangling the name Further, if one set of decls (let's call it That doesn't necessarily seem intellectually difficult; it just smells complex/wide in terms of adequate testing, recursion, making sure you don't miss cases, etc. Does my logic hold up? If so, it makes me kind of favor an earlier option: Just make classes open types. They technically aren't, but anything in flow that takes an |
Class inheritance rejected in feaf58c |
I think there are more problems that arise from copying members and methods that creating a permutation can't solve. For example, when we support importing files, the following would need duplication across files: // A.js
declare module 'A' {
declare export class A {
print(a: A): void
}
}
// B.js
import type { A } from 'A'
declare module 'B' {
declare export class B extends A {
other(b: B): void
}
declare export var b: B
} I think it would cause too many problems for us in the future, and it might be a good idea to put it on hold. You're right in that doing all of this is theoretically easy, but it's a serious smell. Thanks so much for adding the |
Just to make sure I understand, is the classes-as-open-objects a no-go? It seems like it would enable the important parts of inheritance behavior. Also, it occurs to me that we should implement interface inheritance no matter what! I've been focused on classes this whole time, but interfaces are clearly open, right? We could at least knock those out. |
Can you paste an example of what the code generation would look like for open objects? |
Sure; how does this strike you? It's based on how I think about classes, which is, conveniently, "basically open objects"; I may be missing some of the fine points of deep OO. This seems to work in try-reason, or at least doesn't have compile errors!
|
The snippet didn't compile for me in try 😅 I think if I'm reading this right though, all classes become open object types, and we pass them into methods defined on the module, rather than access with If my interpretation is correct, the solution above works, however I think we would be breaking type contracts for a lot of libraries. In the above example, you can pass Is there any way we could constrain those objects to only be passed to certain methods? |
Sorry, could you elaborate/expand on this part?
Maybe an example of where this could misbehave? I think I'm just not parsing the terminology correctly. |
Is this still the best workaround in absence of this feature? module Animal {
type t;
val name: () => string;
}
module Mammal {
type t;
}
external asAnimal : Mammal.t => Animal.t = "%identity"; |
Classes and interfaces cannot inherit from others right now. We should add this.
The text was updated successfully, but these errors were encountered: