Mixin composition
This issue was originally filed by @simonpai
Currently Dart does not support direct mixin composition as mentioned in the document (http://www.dartlang.org/articles/mixins/). However if we have a way to either define M1 * M2 or just make
class X extends Object with A, B {
...
}
typedef Y = Object with A, B;
X, Y valid mixin candidates, it will come in very handy to build flexible class plugins.
Algebraically there is no loophole to define the composition operator, and the association will work perfectly.
Related issue: http://www.dartbug.com/8127
This comment was originally written by @simonpai
link to discussion: https://groups.google.com/a/dartlang.org/forum/?fromgroups=#!topic/misc/0rUZ3eNwyQc
Yes, mixin composition is a useful notion and it would be nice to have it. It won't happen in the first release though.
Set owner to @gbracha. Added this to the Later milestone. Added Area-Language, Accepted labels.
Removed this from the Later milestone. Added Oldschool-Milestone-Later label.
Removed Oldschool-Milestone-Later label.
It would be great if we could have it (say for version 2.0, whenever that comes), since it - along with removing some of the restrictions - would allow the libraries to use mixins in a more consistent and usable manner.
Currently, in order to have a proper ListMixin that is usable by itself, we have to copy (textually) the implementation of IterableMixin, because otherwise the user would have to know to extend with both IterableMixin and ListMixin to get the full behavior. Making a composite mixin, even if only by "class ListMixin = _ListBase with IterableMixin;" declarations, that can be used by itself avoids code duplication while keeping a usable set of mixin classes.
(Remove some more restrictions, and we won't have to have separate Mixin and Base classes, which would be awesome!)
@leafpetersen - is this covered by the super mixins proposal?
@lrhn I think this is still on your wishlist, but I don't think we really need this to track the feature do we? Feel free to re-open if you want to keep this one around.
I think I'll keep it open for now. It is a wish, even for the platform libraries (our ListMixin duplicates code from IterableMixin because we can't reuse code in a mixin).
The new syntax will definitely make it easier to define composite mixins.
Marking as request since this does not specify a specific approach to introducing composite mixins, just the issue that you can't.
Having something as:
mixin A with B {
void methodFromA() {
methodFromB();
}
}
Would be a very nice refinement on the language
Another issue related to mixin composition is that we can't express a generic type T that extends more than one interface. So if I have four mixins, and I want to create a generic method that only accepts objects that implement two of those mixins, I can't do that.
Java supports multiple bounds with & syntax: https://docs.oracle.com/javase/tutorial/java/generics/bounded.html
C# also supports multiple type constraints using commas: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters
I'm trying to do mixin A with B on C where B is an abstract class, and it looks like this is not supported. Is there any known way to proceed without new language features?
@lukepighetti Depends on what you want it to do, but if you want the the mixin A to contain all the members of the mixin B, as well as whatever it declares itself, then no. That's not currently possible, and you need a new language feature to enable it.
That's exactly it. My use case was attaching WidgetsBindingObserver to a custom Store class and hooking into Store lifecycle methods to handle adding/removing the listener. Will keep an eye out for movement in the future. Thanks.
For the record, the SDK contains a SetMixin which includes copies of all of IterableMixin because there is no way to shared mixin code. I'd love to have mixin composition, but it's not particularly high priority because complicated mixins aren't actually being used that much.
If you need to extend the mixin, which contains only abstract methods, you can use implements instead of extends
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class TestProvider extends ChangeNotifier {}
mixin AnotherMixin<T extends ChangeNotifier> on StatelessWidget {
T activateController();
}
mixin Controller implements AnotherMixin {
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) {
return activateController();
},
child: buildWidget(context),
);
}
Widget buildWidget(BuildContext context);
}
class Test extends StatelessWidget with Controller {
@override
Widget buildWidget(BuildContext context) {
return Scaffold(body: Center(child: Text("Tap")));
}
@override
ChangeNotifier activateController() {
return TestProvider();
}
}
The new syntax will definitely make it easier to define composite mixins.
I need mixin composition in my work right now. Since @lrhn mentioned, could anybody help me what's the hard way to implement it in the current version?
I think mixin is a thing meant to be composed. It's wired that they cannot compose their own kinds.
I think mixin is a thing meant to be composed. It's wired that they cannot compose their own kinds.
No more words needed 😉
This would be great! Maybe on the 10 year anniversary of this issue? 😆
Having something as:
mixin A with B { void methodFromA() { methodFromB(); } }Would be a very nice refinement on the language
Not quite the same, but you can do:
mixin A { void testA() => print("Hello from A"); }
mixin B on A { void testB() => testA(); }
class C with A, B { }
void main() => C().testB(); // "Hello from A"
The only difference is that B doesn't come "bundled" with A, you need to mix it in yourself. Being able to fix this with mixin B with A would be nice. But if you forget, the compiler will tell you to mix in A as well. The same result can also be achieved with a comment.
It would be great to see the issue description updated to make it more relevant to the state that Dart is in today and not 2013.
- Dart has added a
mixinkeyword to make the use ofabstract classes as mixins explicit. - As of Dart 2.18, only one level of mixin-based code reuse is possible because mixins can't be collapsed into one mixin i.e. the following:
mixin MixinA with MixinB, MixinCis not possible. The current implementation of mixins in dart is arguably incomplete. - There seems to be consensus across the community and language team that this feature is exclusively good for Dart (!?)
- Example packages that make use of mixins today and would benefit from mixin composition are: https://github.com/simolus3/drift, https://github.com/renggli/dart-xml and basically every other package that declares ASTs or complex DSLs.
- There is a comment (with more upvotes than the issue itself) that provides an example for what mixin composition could look like: https://github.com/dart-lang/language/issues/540#issuecomment-568541790 and it should perhaps be included in the issue description itself.
- (Also, both links are dead.)
Since the amount of upvotes has been used before to highlight the importance of new language features, I think the state that this issue is in right now is unfair to those who want to see Dart support mixin composition.
@modulovalue Thanks for you effort. Although I am the original author, since the issue was ported by DartBot I can't do any editing unfortunately.
Just FYI, the original ticket of the second broken link also got ported to GitHub, but it's mostly irrelevant now: https://github.com/dart-lang/sdk/issues/8127
It would be really great to have this, as currently i am working on an app that includes custom features, where each feature can change a different part of the app.
essentially, I have mixins that define certain methods that allow each function, but I also have mixins that abstract other mixins
for example, if i have a mixin that handles a websocket and pipes the data into relevant methods (say, WebsocketMixin) and i have another mixin that relies on that, reading from one of these methods, enabling the listener, and showing a dialog where necessary. unfortunetely, i cant define a mixin WebsocketDialogMixin with WebsocketMixin i have to do mixin WebsocketDialogMixin on WebsocketMixin which requires that all classes using it has to define both, which isn't intuitive at all.
i dont really see any reason why this cant be easily implemented, as it just abstracts this unnecessary boilerplate.
i really want to be able to just define a
class CustomFeature extends BaseFeature with WebsocketDialogMixin, FileEditorGuiMixin, SomeOtherFeatureMixin {}
as it is i have to do
class CustomFeature extends BaseFeature with WebsocketMixin, WebsocketDialogMixin, FileDetailsMixin, FileEditorGuiMixin, SomeOtherBaseFeatureMixin, SomeOtherFeatureMixin {}
which genuinely sucks
It would be really great to have this, as currently i am working on an app that includes custom features, where each feature can change a different part of the app.
essentially, I have mixins that define certain methods that allow each function, but I also have mixins that abstract other mixins
for example, if i have a mixin that handles a websocket and pipes the data into relevant methods (say,
WebsocketMixin) and i have another mixin that relies on that, reading from one of these methods, enabling the listener, and showing a dialog where necessary. unfortunetely, i cant define amixin WebsocketDialogMixin with WebsocketMixini have to domixin WebsocketDialogMixin on WebsocketMixinwhich requires that all classes using it has to define both, which isn't intuitive at all.i dont really see any reason why this cant be easily implemented, as it just abstracts this unnecessary boilerplate.
i really want to be able to just define a
class CustomFeature extends BaseFeature with WebsocketDialogMixin, FileEditorGuiMixin, SomeOtherFeatureMixin {}as it is i have to do
class CustomFeature extends BaseFeature with WebsocketMixin, WebsocketDialogMixin, FileDetailsMixin, FileEditorGuiMixin, SomeOtherBaseFeatureMixin, SomeOtherFeatureMixin {}which genuinely sucks
I have the same situation and feeling. It's suck when I defining my class like this.
class ZombieModel extends GameObject<ZombieModel> with IsBody, IsCharacter, IsMonster, Breakable, Movable {
}
instead of
class ZombieModel extends GameObject<ZombieModel> with IsMonster, Breakable, Movable {
}
while IsMonster needs to extends IsCharacter which also extends IsBody.
Are there any new developments in this issue? I remembered it after the medium post for Dart 3 release, in my opinion, this could be a great time to add some focus to it.
This would be great! Maybe on the 10 year anniversary of this issue? 😆
Didn't happen, let's hope for the 15 years anniversary!
What does it take to make this happen? I know I'm not the only one wondering why this isn't already a thing.
Isn't it a simple task to have
mixin Bar1 {}
mixin Bar2 {}
mixin Foo with Bar1, Bar2 {}
class A with Foo {}
be syntax sugar for
mixin Bar1 {}
mixin Bar2 {}
mixin Foo on Bar1, Bar2 {}
// or abstract mixin class Foo implements Bar1, Bar2 {}
class A with Foo, Bar1, Bar2 {}
Or is there something I'm missing here?
@TekExplorer In short, the same reason any other thing hasn't happened: This hasn't been made a higher priority than the things that have happened, so the necessary resources have not been spent.
It almost is that simple, at the core. It's all the details that cost.
If you do
mixin Foo on Super1, Super2 with Bar1, Bar2 { members }
then the compiler will have to check that the combined interface Super1 & Super2 is a valid superinterface of Bar1, and that (Super1 & Super2) with Bar1 is a valid superinterface of Bar2.
Should be doable, but might need a little spec-work to apply a mixin to a combined supertinterface, without requiring that the result is fully defined yet.
(Usually a mixin Foo on Super1, Super2 { ... } is required to have either a consistent member signature from the supertypes, or declare its own member signature to hide any unsolved conflicts, so that the interface of Foo itself is well-defined. Here we need to figure out whether it's OK for Super1 & Super2 to have an unresolved conflict that neither Bar1 nor Bar2 overrides, as long as Foo does. It probably is, but we need to check and make sure, cross t's and dot i's, and maybe expand the model to "combined and overridden super-interfaces".)
Every mixin remembers which super.member invocations its mixin members do, because it's required that the (potentially abstract) superclass it's applied to has concrete implementations of those members.
We need to compute that for mixin Foo on Super1, Super2 with Bar1, Bar2 { ... } too. Probably fairly trivial, we just need to do it, and make sure we're not missing something. In some cases the super-member is now known, because it comes from another mixin. So it's definitely all the super-member invocations of Bar1, plus the ones of Bar2 where Bar1 does not provide a valid concrete implementation. (I don't think Bar1 can introduce an implementation which is invalid for Bar2 to call, not if we already checked that Bar2 can be applied on top of Super1&Super2 with Bar1. It would have failed there already.) And then all the super-invocations of Foo itself, minus those implemented by Bar1 or Bar2.
Seems doable.
Also, can you do mixin class Foo with Bar, Baz { ... } too? Probably. I don't see any issues.
Then, as you say, the mixin application of class C extends SomeSuper with Foo {} would effectively be equivalent to
class C extends SomeSuper with Bar1, Bar2, Foo { }
where Foo here is only the members it declared itself. That's the semantics, there was never really much doubt about what applying a composite mixin would do.
The static checking is a little more complicated, because we want to phrase errors in terms of the user's Foo declaration, not its desugaring.
So, if SomeSuper does not implement Super1 and Super2, that's the error we show (not that SomeSuper&Bar1 is not a valid superinterface for Bar2).
If SomeSuper does not provide a concrete implementation of a super.member needed by any of Bar1, Bar2 or Foo itself, then we should say that it doesn't satisfy the requirements of Foo, the type user actually wrote in with Foo.
(We can also say that it's a requirement it gets from, fx, Bar1, but that should be secondary. It might not make sense to the end user, who doesn't have to know that Foo is a composite mixin.)
We should also define what happens when you combine composite mixins, like:
mixin Baz on Super1, Super2, Super3 with Foo, Qux {}
where Foo is the composite mixin above.
It should obviously work, we just need to define it properly, since that's now also a composite application.
Then we should consider whether this change would interact badly with other possible future language changes. Obviously that's mainly the ones we've actually thought about, like adding constructors to mixin (can't find issue specifically for it, mentioned here: https://github.com/dart-lang/language/issues/1605#issuecomment-1543650950, and also things like #3242).
I actually think the specified behavior here is very compatible with the design for constructors (it's simple and based on just doing repeated mixin application, so if we can do it once, we probably can do it repeatedly), so that shouldn't be a problem.
All in all, I think it's doable, but a language change is never trivial. Eagerly desugaring one feature into another will give worse error messages, if it starts referring to the result of desugaring, instead of the user's original code. The specification has to be changed, we have to go through all of it (which we won't, but we ought to) in order to check how the new feature interacts with existing features. Documentation, tests, release process. And implementation.
I feel like we can almost treat a combined mixin as though the composite (the mixin that mixes other mixins) is treated like a normal mixin that happens to have all of the code from the other mixins, pre-applied as through normal mixin rules, and just so happens to implements those other mixins.
there's nothing actually wrong with this code:
mixin Foo1 {
int i = 0;
}
mixin Foo2 {
void doThing() => print('hi');
}
/// mixin Foo3 with Foo1, Foo2 {}
mixin Foo3 implements Foo1, Foo2 { // mixin class works the same
@override
int i = 0;
@override
void doThing() => print('hi');
}
class RealClass with Foo3 { // includes Foo1 and Foo2
int idk() {
doThing();
return i++;
}
}
It just violates DRY
therefore, we could say that any compiler checking is done on this virtual "flattened" mixin
the only real concern as far as I can tell is that each method just needs to remember where it actually came from.
All mixin rules apply exactly as they do now, since its effective syntax sugar. There is very little additional logic, on the high level.
I do recognize that the analyzer would not necessarily see this as a trivial change, but as far as the language itself is concerned, we can mostly reuse logic.
from time to time I end up needing mixin composition.
is there any workaround to this?