-
Notifications
You must be signed in to change notification settings - Fork 205
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
The base
keyword is problematic
#3844
Comments
The properties of base are useful, maybe not for your specific use cases, but they are useful:
One example is this allows you to ensure specific validation or setup logic is ran on object creation.
This prevents runtime errors as it allows you to safely assume private members are available. Otherwise any call to a private member could result in a runtime error.
This allows you to evolve your API safely without breaking changes (or at least, far fewer breaking changes), and is very useful for package authors and their consumers alike. |
@jakemac53 thanks for helping me understand your perspective! Unfortunately I don't think I'm able to respond in a concise way, so apologies in advance for the wall of text.
A constructor body could have important logic, and in that case it seems understandable that a developer would want to add the But on the flip side, it's also very possible that a developer would want to use an API that requires And even if the /* will not compile if SomeClass is a base class */
class A extends SomeClass {}
class B extends SomeClass implements A {}
That's a good point—here's an example that throws (without triggering any static analysis warnings): /* a.dart */
class A {
void _evaluate() {
// This is an important method; it's used outside the class declaration.
}
}
void functionA(A a) {
a._evaluate();
} /* b.dart */
import 'a.dart';
class B implements A {}
void functionB(B b) {
functionA(b);
}
We could add a linter rule that recommends adding a
If an API is being actively worked on, there are many potentially breaking changes:
The That being said, it's not often that a developer knows in advance which classes will have new members added in the future and which classes won't. And what if someone wants to implement the class and is fine with adapting to breaking changes? A decorator that triggers a linter rule would make more sense than a keyword that causes a compile-time error. Back when I was brand new to Dart, I wasted a lot of time debugging dumb mistakes, but implementing a class that should have been extended was never a problem, since any time I started using a new API, I would always copy some example code and then tweak it from there. Since accidentally breaking a program by using |
I'll also disagree that The goal of You can't absolutely guarantee that a member won't be overridden (or that it must be overridden and must call its super). Not overridden would be like a
... it would not allow for type promotion if the type can be implemented. The type would need to be the current About your solutions:
Just don't use it then.
Basically, make it ignorable.
That makes every class a mixin. Just make the classes that need it mixins. (Let's discuss composite mixins if the current ones are too restrictive.)
That suggested change should be a per-member modifier. What is the problem you're really trying to solve? So what is the behavior that you want to achieve, which no modifier gives you today? And what has that got to do with (I sometimes wish I had a non-inherited "do not implement" modifier, for a class that has no reason to exist other than work as a superclass. It's not that it's a problem if you implement the type, it's just meaningless. Something like |
@lrhn I really appreciate you sharing your thoughts.
Yes, my concern is that at some point I'd want to use somebody's API, and the At this point, I only have 1 point of disagreement:
That's only partially true, since type promotion could still work within the class declaration. class A {
base final Object value;
const A(this.value);
void classMethod() {
if (value is int) print(value.isEven); // promoted
}
}
// also works in a class declaration that extends A
class B extends A {
const B(super.value);
void classMethod2() {
if (value is Size) print(value.width); // promoted
}
} Allowing implementation wouldn't be problematic, since everything that benefitted from type promotion would be overridden: class C implements A {
@override
Object get value {
// ...
}
@override
void classMethod() {
// ...
}
} But it would prevent type promotion any time the type is used as a parameter: void foo(A a) {
if (a.value is int) print(a.value.isEven); // error
} After reading these comments and thinking it over a bit, I don't think any of the solutions I proposed in this issue would be ideal. I'm good with closing this one; perhaps in the future we could improve or expand on the class modifier in a better way. |
interface
is greatA developer can use
interface
in a class declaration to show that it's meant to be implemented, not extended.If
C
isn't declared in the same library asA
andB
, it's a super easy refactor:Switching from
class A
tointerface class A
provides no functional benefit, but it makes the class more descriptive and easier to understand with virtually no downside.base
is too restrictiveMuch like
interface
, thebase
keyword provides no functional benefit. And unlikeinterface
, it creates significant problems.Unless all 3 classes are declared in the same library, this will not compile. A single-line fix isn't possible: the
C
class declaration would need to be restructured or removed entirely.Solution?
A few possibilities:
base
keyword doesRemove the keyword
From the dart.dev documentation:
I don't think any of this is useful; let me know if you disagree.
Make it a decorator instead
no_logic_in_create_state
has an "info"-level severity, so it's not a huge deal to break the rule if the situation calls for it. I believe the same severity is appropriate for an "extend this class, don't implement it" rule.Allow extending multiple classes
This would allow for enums extending other types, but adding this language feature might be significantly more trouble than it's worth.
Change what
base
doesRather than "the opposite of interface", perhaps the
base
keyword should be "the opposite of abstract".Currently:
interface
should be implemented, not extendedbase
should be extended, not implementedProposal:
abstract
"must be overridden"base
"must not be overridden"This would be a breaking change that
provides no functional benefitjust kidding, it allows for type promotion!I recently learned that
abstract
can apply to a class member, not just the class itself:Maybe the same could be true for
base
!The text was updated successfully, but these errors were encountered: