-
Notifications
You must be signed in to change notification settings - Fork 554
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 basic interface
capabilities.
#1126
base: main
Are you sure you want to change the base?
Conversation
Well, I for one, am certainly interested in this proposal though I'm curious about what exactly you have in mind as I can't tell much from what you've posted so far. At present, I simply use abstract classes for interfaces i.e. no constructor and methods are just shells to be implemented by the child classes. For example: class Comparable {
compare(other) {
// This should be overridden in child classes to return -1, 0 or 1
// depending on whether this < other, this == other or this > other.
}
< (other) { compare(other) < 0 }
> (other) { compare(other) > 0 }
<=(other) { compare(other) <= 0 }
>=(other) { compare(other) >= 0 }
==(other) { compare(other) == 0 }
!=(other) { compare(other) != 0 }
}
class Date is Comparable {
//...
compare(other) {
// code to compare dates
}
} This works fine as long as I only want to implement a single interface and don't want to also inherit from a 'concrete' class, given that we don't have multiple inheritance. So do you have something like this in mind: interface Comparable {
// code as above
}
class Date implements Comparable {
// code as above
} except that a class could implement multiple interfaces as well as inherit from a single concrete class? If so, then that would definitely be useful though a problem to resolve would be how to deal with inherited and/or implemented methods with the same signature. Another interesting possibility is that we might be able to use interfaces which provide default implementations of their own methods as a kind of 'mixin' which all implementers could share. |
Actually, thinking about it some more, I'm not sure that dealing with inherited and/or implemented methods with the same signature would be much of a problem for Wren. If the child class included an implementation of such a method, then that could be considered as implementing the interface(s)'s methods as well as over-riding the inherited method. If the child class didn't include an implementation of such a method (I imagine you won't want to force a child class to implement all interface methods given the limitations of a single pass compiler), then the inherited method would be used if there was one, otherwise the interface methods would be chosen in the order they appear in the class definition and might be empty in any case. |
For now I think I would go simple for I'm considering to add For now, I think What @PureFox48 suggest would make them act more like |
OK, thanks for the clarification. I agree, of course, that it's best to walk before we try and run :) |
Incidentally, won't you need to reserve Even if interfaces are just going to provide pure virtual methods (and possibly static methods), there will need to be some way to distinguish them from ordinary classes. |
Yes, if it takes that form. Unless the final decision is to go
That way, we can have the basic functionality to toy with and can decide these more heavy decisions later, while (I guess) preserving backward compatibility. |
This is likely changing, I've been testing it the last few months after discussing with Bob and it's been valuable in practice (and removes one surprise for users).
Side note @PureFox48 as you know we don't have type info, so the compiler side is basically pass through on this. For Wren it seems the value in just confirming the shape of something matches expectations without relying on inheritance, akin to a structural subtype test for code. I know I rely on the shape of things implementing the shape of something, having it be able to be tested makes intent a lot clearer too in the code. method(expect_shape_of_rect) {
//typically compiler doesn't know the type of arg, or it's class, nor has details about the rect shape
if(!(expect_shape_of_rect implements Rect)) Fiber.abort("expected a Rect-like value")
expect_shape_of_rect.width ...
} Optional type annotations raise the typical questions about union and multiple interfaces, e.g typescript has |
What I was originally thinking here is that if, say, you defined an implementing class before an interface in the same module, then the compiler wouldn't know what methods the class needed to implement. But I've just realized that you can't even do this just now for inheritance: class B is A {}
class A {} If you try to do that you get: Class 'B' cannot inherit from a non-class object. So there would be no reason to expect it to work for interfaces either.
Interesting! Can't think of any obvious drawbacks to static methods being inherited (
Yeah, that would be a 'three pipe' problem for sure :) |
Updated to support class definitions, add documentation. |
There are obvious drawbacks to enabling static functions: constructor, constructor static methods, singleton pattern implementation and probably others. You don't want them to be inherited by default. |
Well, I’d assumed that constructors, which are a combination of a static method to create an instance and an instance method to initialize it, wouldn’t be inherited but I’d overlooked static methods which call constructors which Wren needs because of the lack of constructor chaining and also to implement the singleton pattern. You’re right that we certainly don’t want those methods to be inherited by default so it looks like we’d have to introduce a ‘sealed’ keyword (or similar) to stop that from happening if static methods are to become inheritable. Getting back to this proposal, I see that you are in fact forcing the subclass to implement the interface methods if ‘subclassResponsibility’ is used. Rather than using some special word like that how about just omitting the body entirely in the interface? |
This is next phase of the plan, currently this is the minimal requirement to make the idea work. And I want to wait for approval or comments for update, before moving on. It will probably implemented as a 'mixin' with the extra requirement to not allow local variables (to save on complexity). That way, we can allow pure virtual and basic helper methods implementations. |
I have struggles at implementing it. I don't know what to do. 'interface' and 'mixin' are both interesting features. problem being that |
In that case I think I'd keep it simple and just do something like this for now: interface Rect {
width
height
}
class Rectangle implements Rect {
construct new(width, height) {
_width = width
_height = height
}
width { _width }
height { _height }
} That would define the 'shape' of a rectangle but leave the implementation of the methods to the implementing class. Judging by her remarks above, that seems to be all @ruby0x1 is wanting in any case. It would be easy for folks to understand and, if a class implemented multiple interfaces containing a common method signature, the class method could be considered to satisfy all of them. We can leave 'mixins' for another day. |
I'm not sure if this is exactly what you had in mind but, even with the simple scheme outlined in my previous post, classes could still have a The operation I think you said or implied earlier that your idea (at least initially) was that a class would have a choice:
This raises the technical question of how could a class choosing 2. also inherit from Object? One solution would be for all Interface objects to inherit directly (or perhaps indirectly via an |
I don't see the point of interfaces in a language like Wren that have duck typing. There is no type checking in Wren at compile time and interfaces are a kind of compile-time type checking (does this class implements these methods?). At runtime, it is easy to see if a class does not implement a method : an error is generated. On the contrary, mixins would add much value. What is in #1126 (comment) is a mixin, not an interface. |
Well the advantage of having interfaces in Wren would come through testing - instead of having to test for each method individually, you could test for the whole lot in one go via the Even if you don't test, there would still be the advantage of code failing earlier without waiting for an unimplemented method to be called. Also, as @ruby01 said earlier, interfaces would make the intent of the code clearer too. You're right, of course, that the example I posted earlier where I currently inherit from an abstract class would require a mixin rather than a simple interface to reproduce, given that I'm providing default implementations of the ordering operators. I think we're all agreed that mixins would add plenty of value but it sounds, from what @mhermier is saying, that there are serious difficulties in implementing mixins and then treating interfaces as a special case. If those difficulties could be overcome, then that would represent a much more valuable solution overall. |
The difficulties that I have is more logical than implementation wise. The problem is that when you add |
Well, I agree that we don't want to introduce both mixins and interfaces as separate entities into Wren - that would be too much extra baggage. Given that we can already implement a mixin as an abstract class, I wonder whether a better approach would be to allow a class not only to inherit from another (single) class but to additionally implement one or more interfaces. This is what C# originally did (as a weaker form of multiple inheritance) and it seemed to work well enough. The interfaces would take the simple form I outlined above. This wouldn't be perfect as we would only be able to inherit from a single mixin though, in practice, you can often arrange matters so that the mixins inherit from each other. However, name resolution shouldn't be a problem. If there were a signature clash, the interface(s) would be satisfied by either a method declared in the implementing class itself or inherited by it. The problem I raised earlier about how a class, implementing one or more interfaces, could then inherit from Object would be solved automatically by this approach without the interface(s) having to themselves inherit from Object. In the class definition, after Personally, I'd like to see an Incidentally, in C# 8.0, they're now allowing interface methods to have default implementations so the above approach wouldn't necessarily rule out having multiple mixins in a future version of Wren. |
In Raku (and the ports of its object system to Perl and Javascript, Moose and Joose), interfaces are fully subsumed by "roles," which is their implementation of traits, intended to serve as the behavioral dimension of the type system. (That is, class Square "is" Rhombus and Rectangle, but it "does" Drawable, Translatable, etc.) The most interface-like roles solely declare method names and signatures. It's a compile-time error for a class definition to declare that it does a role, but then end without implementing those methods. (This only slightly impedes duck typing. If a class has all the right methods but does not declare that it does the role, and for whatever reason you're not free to add that definition, you can get the compile-time safety by declaring a class to inherit from it and do the role, or you can use the Less interface-like roles can also declare attributes and define methods. When a class declares that it does such a role, the declarations are copied into the class and compiled (the class "consumes" the role). Crucially, it is a compile-time error for a class to consume two roles that give different definitions for the same method name and signature, unless the class resolves the conflict by providing its own definition. (Definitions directly in the class supersede definitions composed in from roles, which supersede definitions inherited from superclasses. This generally turns out to be what you want. The superseded definitions are still available, but under qualified names. I believe something similar happens when attribute declarations have conflicting types.) These compile-time errors manage to avoid a lot of the pitfalls of multiple inheritance, while still adding code reuse to all the benefits of interface types. A role definition can even include further does-role declarations, and there's no diamond inheritance problem or thinking through a method resolution order; if consuming roles gets you the same definition twice, no problem, and if it gets you conflicting definitions, the compiler makes you be explicit about what to do. |
Interesting. I like the way that Raku resolves conflicts between role methods having the same signatures but different definitions by insisting that the consuming class provides its own definition. It's a possible approach for Wren if we just do bare interfaces for now, for which conflicts are not a problem, but add mixins later. |
I'm currently really busy with the end of the year season. To make things worse, I'm currently hill, so my work on this is delayed for now. |
Hi,
The following change sets adds basic
interface
capabilities to the language.For now, only the
implements
operator is added. But I also plan to addclass
definitions checks soon (I won't have the time to implement it, until the end of weekend).If there is enough interest, I consider adding
interface
s definitions to the language. I started prototyping it but It needs to be more polished.