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

Allow interface as its own construct (not just class modifier) #3736

Open
mmcdon20 opened this issue Apr 29, 2024 · 4 comments
Open

Allow interface as its own construct (not just class modifier) #3736

mmcdon20 opened this issue Apr 29, 2024 · 4 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@mmcdon20
Copy link

In dart there is support already for creating interfaces using a class modifier, but the existing support is weird and unintuitive.

Consider this example:

abstract interface class Vehicle {
  final String make;
  final String model;
  Vehicle(this.make, this.model);
  
  void moveForward() {
    print('$make $model is moving forward!');
  }
}

class HondaCivic extends Vehicle {
  HondaCivic(): super('Honda', 'Civic');
}

For starters, it is a bit of a keyword soup to remember abstract interface class whenever you want an interface, and you almost never would want just interface class.

It is also weird that

  1. you can define class fields in an interface
  2. you can define constructors in an interface
  3. you can define method bodies in an interface
  4. you can extend an interface from within the same library

I propose allowing a separate interface construct that would not allow any of the above.

interface Vehicle {
  final String make; // error cannot have properties use getter instead;
  final String model; // error cannot have properties use getter instead;
  Vehicle(this.make, this.model); // error cannot have constructor
  
  void moveForward() { // error cannot have method body
    print('$make $model is moving forward!');
  }
}

class HondaCivic extends Vehicle { // error cannot extend interface
  HondaCivic(): super('Honda', 'Civic');
}

The errors should guide the user towards using interface as intended:

interface Vehicle {
  String get make;
  String get model;
  void moveForward();
}

class HondaCivic implements Vehicle {
  @override
  final String make = 'Honda';
  @override
  final String model = 'Civic';
  @override
  void moveForward() {
    print('$make $model is moving forward!');
  }
}
@mmcdon20 mmcdon20 added the feature Proposed language feature that solves one or more problems label Apr 29, 2024
@LewisHolliday
Copy link

For starters, it is a bit of a keyword soup to remember abstract interface class whenever you want an interface, and you almost never would want just interface class.

I'm not sure that constructable interfaces, that is, interfaces that even importing libraries can construct, carry their weight. I think they should be limited such that only the local library can construct them. That's because I think you can sidestep the 'cannot extend' restriction by constructing an instance, then wrapping it in a class that implements its interface, which effectively allows you to inherit its implementation:

// foo.dart
interface class Foo {
	m(){}
}
// bar.dart
import 'foo.dart';
class Bar implements Foo {
	final foo = Foo();
	m() => foo.m(); // Sort of like extending; just more work!
}

However, that isn't quite the same as extending, because you don't inherit, for free, the private implementation/members present in the superclass. But if there's a private interface in the superclass, then why give the superclass the interface modifier at all? The author of the superclass is creating potential exceptions for no benefit.

TLDR: I think non-abstract interfaces should have no public constructors. I might be way off on that, but I can't think of a good reason for wanting an interface that an outside library can construct. Are there any? If not, that raises another question: should there be a warning or lint against non-abstract interface classes with public constructors?


  1. you can define class fields in an interface

  2. you can define constructors in an interface

  3. you can define method bodies in an interface

  4. you can extend an interface from within the same library

I propose allowing a separate interface construct that would not allow any of the above.

I can kind of see where that is coming from. It is definitely weird; especially point 2. But I'm not sure that we should have more than one way to do the same thing. I think these are my issues:

  1. abstract interface class is too wordy
  2. It's too easy to write interface class, which allows outside libraries to construct it
  3. Nobody should want to construct an interface class except the inside library

However, I think there is value in allowing an interface to be constructed and extended from within the same library. And, more importantly, I think there is value in all interfaces being potentially extendable from within the same library. Right now, an author can add an implementation to their abstract? interface class, which allows extending. Is there something I'm missing that makes extendable interfaces problematic? Again, in my eyes, the problem is with interface class, which allows outside libraries to construct it.


I have a proposal that may kill two birds with one stone:

Inside Library Outside Library
abstract interface implement, extend implement
interface implement, extend, construct implement

The idea is that an interface's constructors would be explictly/implicitly private. It also hinges on the premise that public constructors on interfaces are bad. My proposal removes class, which would be too confusing if it could not be constructed, despite being non-abstract. It's also shorter (but not as short as OP's proposal so maybe not quite the full bird). The other bird killed is that the outside library can no longer construct an interface.

@mmcdon20
Copy link
Author

mmcdon20 commented May 1, 2024

However, I think there is value in allowing an interface to be constructed and extended from within the same library. And, more importantly, I think there is value in all interfaces being potentially extendable from within the same library. Right now, an author can add an implementation to their abstract? interface class, which allows extending. Is there something I'm missing that makes extendable interfaces problematic? Again, in my eyes, the problem is with interface class, which allows outside libraries to construct it.

None of that is allowed in Java or C# interfaces (at least as far as I'm aware). Constructing and extending an interface within the same library is not what I assume most people would expect an interface to be able to do. But my proposal is not to replace or remove interface as a class modifier, but add a new way to declare an interface, similar to how mixin is allowed as both a class modifier and its own construct. So you would still be able to use the interface class modifier if you needed to construct or extend the interface within the same library.

@lrhn
Copy link
Member

lrhn commented May 1, 2024

I'd be fine with allowing interface not followed by class to be a shorthand for abstract interface class.
(Heck, I've suggested it before myself.)

I don't see any advantage in adding more restrictions than what abstract interface class has today.
It prevents you from doing things that you can just choose not to do anyway, and it will only matter inside the same library whether you do it or not.

Having interfaces with no implementation is not a goal. It's not "using interfaces as intended", because there isn't a single intent for what you can use interfaces for. There is no problem with having implementation, if our actually used.
The analyzer should be able to tell you if an abstract interface class has implementation, and it's never extended inside the same library. If it doesn't today, it's at least a possibility.

@mmcdon20
Copy link
Author

mmcdon20 commented May 1, 2024

I'd be fine with allowing interface not followed by class to be a shorthand for abstract interface class. (Heck, I've suggested it before myself.)

Well it would at least solve one of my complaints with the existing implementation (too wordy and hard to remember), but I would prefer if it went further than that.

I don't see any advantage in adding more restrictions than what abstract interface class has today. It prevents you from doing things that you can just choose not to do anyway,

Why have any compile errors? The programmer could simply choose not to do any of the things the compiler would have prevented them from doing. :)

and it will only matter inside the same library whether you do it or not.

Yes but that is a common scenario, I don't have the data to prove it, but I suspect in a many cases, maybe even most cases, an abstract interface class is defined in the same library as classes that implement it.

Take the flutter repo for example, I found 7 different libraries where abstract interface class was used and 0 where interface class is used. In 5/7 libraries the interfaces were implemented in the same library.

interfaces pure interface? implemented in same library? notes
CliEnum yes* no includes static members
DecorationImagePainter yes yes n/a
ChipAttributes, DeletableChipAttributes, CheckmarkableChipAttributes, SelectableChipAttributes, DisabledChipAttributes, TappableChipAttributes yes yes n/a
PredictiveBackRoute yes yes n/a
PlatformLocation yes yes n/a
HitTestable, HitTestDispatcher, HitTestTarget yes no n/a
RenderAbstractViewport yes* yes includes static members, also extends RenderObject

Having interfaces with no implementation is not a goal. It's not "using interfaces as intended", because there isn't a single intent for what you can use interfaces for.

Surely individual programmers have an intent for what they wish to use their interfaces for, and I suspect a pure interface is the most common intent.

There is no problem with having implementation, if our actually used. The analyzer should be able to tell you if an abstract interface class has implementation, and it's never extended inside the same library. If it doesn't today, it's at least a possibility.

The analyzer does not do that. Nor are there any lint rules about using an interface in an impure way.

The only relevant analyzer warning I am aware of is when you reopen an interface.

The class 'HondaCivic' reopens 'Vehicle' because it is not marked 'interface'
Try marking 'HondaCivic' 'interface' or annotating it with '@reopen'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

3 participants