-
Notifications
You must be signed in to change notification settings - Fork 214
Is mixin
for non-supermixins a good idea?
#33
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
Comments
More arguments can be found in flutter/flutter#22290. For example:
|
The problem with allowing any (no-constructor, Adding tests cannot catch this. If someone else, in a package you never heard of, is using your class as a mixin (maybe even on a const class), then you break their code if you
These are not changes that would normally count as breaking, so most likely you won't do a major-version increment when you refactored the class to share some functionality with another class through a private super-class - but if they update their dependencies, their code stops working. So, any API containing any class that can be used as a mixin, should do a major-version increment whenever they change such a class so it can no longer be used as a mixin. |
Why don't we simply answer "Yes, it is indeed a good idea to use The point is that this is a contract that involves the author(s) of M and the clients of M, and the software artifacts containing M are simply going to be more maintainable when such a tightly coupled piece of reusable code as a mixin is marked explicitly as such. Do we need a 'resolution-yes' label? ;-) |
I don't see why mixins are special here. Any class can be used as an interface too, with the same problems. |
That's one of the reasons we're looking into sealed classes and the ability to express a class that cannot be implemented. The language already supports this for a hand-picked set of magic classes — bool, double, num, int, and String. If it's useful for those types, it's likely useful for user types. |
Sure, but most classes can't be sealed since they're intended to be subclassed, and almost all classes need to be implementable for mocking purposes. |
As far as I know, we don't actually have any real empirical data to justify a claim like that. |
More than that. In practice, few developers write tests.
One special thing about mixins is that we are trying to fix them now. We are currently not trying to fix interfaces, which, I agree, also have problems. I guess I don't understand what conclusion this argument is trying to reach. I'm also not sure if the problem are the same. For example, if a class is also used as an interface, then adding a Classes-used-as-interfaces do have issues though, and I think they should be addressed at some point. Some background is documented here.
Perhaps, but also most classes do not need to be fast. In practice, only a small portion of code is on the hot performance path, and that's where you will see benefits from things like Not that this particular issue has to do with performance. I'm just doubting the logic in the claim that a feature should be rejected because of low usage count. I think a feature's inclusion should be evaluated according to its overall impact. |
AFAICT, no conclusion has been reached yet. |
My opinion is definitely that, yes, it is a good idea to use I'm not sure what it would take to close this issue, though. |
Me too. But, either way, I think this is a question about best practices and not something actionable in the language repo. Maybe it would be worth filing a bug for what "Effective Dart" should say here, but that's a separate repo. |
The goal of the issue is to get a recommendation from the language team so we could follow it in Flutter and steer our users towards it via analysis_options_user.yaml. If the recommendation is to use |
And what is the solution? effective_dart abuses about using WidgetsBindingObserver as a mixin, but it's a code from the example: https://api.flutter.dev/flutter/widgets/EditableTextState-class.html |
My strong recommendation is to never use a class as a mixin and never design a class as intended to be used as a mixin. If you change all current classes used as mixins to be declared as mixins, and change all uses of those classes as superclasses from We could introduce an intermediate |
I don't really see the value in losing the ability to extend a mixin or mix in a class. Can you elaborate on what the problem is with |
The problem with allowing you to derive mixins from class declarations is that it's not properly signaled and contained. It's opt-out instead of opt-in, and changing it later is a breaking change (aka. accidental lock-in). You can derive a mixin from a class declaration iff
Nothing about that screams "intended as a mixin", just "not incompatible with being a mixin". The resulting mixin can be applied to any class, but cannot do That's much more restricted than what you can do with a We'd be able to say that the only thing you can mix in is a Needing to be declare as a So, using classes as mixins is dangerous because you can use classes that are not intended so as mixins. Because the rules are enabling being used as a mixin from secondary properties of the class, not a deliberate documented desire to be a mixin, it's just bad language design. You can accidentally enabled your class being used as a mixin, and you won't find out until someone uses it and you break them. It should be opt in. The SDK has deliberately added constructor declarations to some classes to prevent it from being used as a mixin (after realizing someone was doing it). It's easy to forget to do that (and also, why should you), but we were not in a position where we could just ignore it and possibly break people later. And since a tl;dr: The real problem is not that |
I hear what you're saying. There's downsides too, though. Things "are" change notifiers, which is expressed by saying "extends" (gives an "is-a" relationship, as opposed to mixins, which is conceptually more of an "includes-a" relationship). It seems like the concern you describe can be entirely resolved by just having the annotation and lint behaviour proposed in dart-lang/sdk#58543, without needing to change the language. That way, people don't use arbitrary classes as mixins except those that opted-in. |
@Hixie, is the problem of extending a ChangeNotifier that Flutter wants users to be able to include |
Yeah sometimes people just want to mix in ChangeNotifier to other elaborate class hierarchies. |
So it seems like
These two seem similar enough to merge into one. |
The vast majority of uses of ChangeNotifier that I'm aware of are just using it as a superclass with |
That's equally true of mixins. Consider the class hierarchy: List > Iterable > Object. I think most of us would agree that a List "is-a" Iterable. But if you apply a mixin, you get the exact same thing: MyClass > ChangeNotifier > Object. So a MyClass "is-a" ChangeNotifier just as much as a List is-a Iterable. I understand many users don't have that mental model, but that's I think largely because mixins are uncommon and we haven't worked hard to give users a mental model for them. Mixins are superclasses. They're just superclasses whose own superclass is pluggable. |
While that's technically true, it's not a mindset that's supported by the language. The language uses "extends" vs "with". Extending something sounds like an "is-a" relationship. Saying "with" does not, it sounds more like a "has-a" or "includes-a" relationship. I think we'd be fighting an uphill battle which would make the language and framework harder to understand if we tried to push people to mix in |
I think I'm somewhat in agreement with @Hixie here. Extending is by far the simplest and most familiar way for a user to get access to some shared functionality in an OO language, and if that affordance works for users in the 95% case, then it feels like we should provide the ability for library authors to offer it (e.g. |
Do we need I'm not keen on supporting |
I'd be fine with the compiler complaining if you tried to mix in a class where there are concrete members in superclasses of the class you're mixing in. |
(FWIW the declared superclasses don't seem to completely disappear, only their implementations do; for example, you still have to implement those interfaces.) |
When using // model.dart
abstract class Model with ChangeNotifier {
/// Initialize data here.
Future<void> init();
}
// user.dart
class UserModel extends Model {
Future<void> init() => loadUserData();
Future<void> updateUser() async {
Firestore.updateUser(await loadUserData()); // some update
notifyListeners();
}
} I always mix-in So IMO, getting users to think of |
We now have |
Uh oh!
There was an error while loading. Please reload this page.
I'm filing this issue as a place to discuss whether using the new
mixin
syntax for non-supermixins is a good idea. I do not wish to pollute issue #32 with this discussion, as I'd like that issue to be focused on the semantics ofextends
w.r.t.mixin
.The arguments presented so far:
@Hixie (#32 (comment)):
@yjbanov:
mixin
comes certain restrictions on what you can do with the class, such as:These properties protect mixin authors from accidentally breaking their users. I believe this is an important property for keeping the Flutter/Dart ecosystem healthy. Mixin usage is much higher than mixin authorship and therefore the cost of using
mixin
vsclass
is dwarfed by the stability guaratees themixin
semantics provide.Some examples in Flutter that may be used as super-classes or as mixins:
Updates to the original issue description:
The text was updated successfully, but these errors were encountered: