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

Mixin composition #540

Open
DartBot opened this issue Apr 2, 2013 · 28 comments
Open

Mixin composition #540

DartBot opened this issue Apr 2, 2013 · 28 comments
Labels
request Requests to resolve a particular developer problem

Comments

@DartBot
Copy link

DartBot commented Apr 2, 2013

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

@DartBot
Copy link
Author

DartBot commented Apr 2, 2013

This comment was originally written by @simonpai


link to discussion:
https://groups.google.com/a/dartlang.org/forum/?fromgroups=#!topic/misc/0rUZ3eNwyQc

@gbracha
Copy link

gbracha commented Apr 2, 2013

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.

@kasperl
Copy link

kasperl commented Jul 10, 2014

Removed this from the Later milestone.
Added Oldschool-Milestone-Later label.

@kasperl
Copy link

kasperl commented Aug 4, 2014

Removed Oldschool-Milestone-Later label.

@lrhn
Copy link
Member

lrhn commented Sep 4, 2014

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!)

@munificent munificent changed the title Support mixin composition Mixin composition Dec 16, 2016
@natebosch
Copy link
Member

@leafpetersen - is this covered by the super mixins proposal?

@leafpetersen
Copy link
Member

@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.

@lrhn
Copy link
Member

lrhn commented Sep 6, 2018

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.

@lrhn lrhn reopened this Sep 6, 2018
@munificent munificent transferred this issue from dart-lang/sdk Aug 22, 2019
@munificent munificent added the feature Proposed language feature that solves one or more problems label Aug 22, 2019
@lrhn lrhn added request Requests to resolve a particular developer problem and removed feature Proposed language feature that solves one or more problems labels Aug 23, 2019
@lrhn
Copy link
Member

lrhn commented Aug 23, 2019

Marking as request since this does not specify a specific approach to introducing composite mixins, just the issue that you can't.

@feinstein
Copy link

Having something as:

mixin A with B {
  void methodFromA() {
    methodFromB();
  }
}

Would be a very nice refinement on the language

micimize added a commit to micimize/major that referenced this issue Mar 2, 2020
will have access to imported fragment declarations.

This is necessary because there is no mixin composition in dart:
dart-lang/language#540
@stephenbunch
Copy link

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

@lukepighetti
Copy link

lukepighetti commented Jan 24, 2021

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?

@lrhn
Copy link
Member

lrhn commented Jan 25, 2021

@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.

@lukepighetti
Copy link

lukepighetti commented Jan 25, 2021

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.

@lrhn
Copy link
Member

lrhn commented Jan 25, 2021

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.

@SergeyShustikov
Copy link

SergeyShustikov commented Mar 25, 2021

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();
  }
}

@johnwangwjq
Copy link

johnwangwjq commented Jun 10, 2021

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.

@matsp
Copy link

matsp commented Jun 10, 2021

I think mixin is a thing meant to be composed. It's wired that they cannot compose their own kinds.

No more words needed 😉

@etegan
Copy link

etegan commented Feb 7, 2022

This would be great! Maybe on the 10 year anniversary of this issue? 😆

@Levi-Lesches
Copy link

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.

@modulovalue
Copy link

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 mixin keyword to make the use of abstract 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, MixinC is 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: Mixin composition #540 (comment) 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.

@simonpai
Copy link

@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:
dart-lang/sdk#8127

@TekExplorer
Copy link

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

@shtse8
Copy link

shtse8 commented Dec 22, 2022

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

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.

@FMorschel
Copy link

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.

@mateusfccp
Copy link
Contributor

This would be great! Maybe on the 10 year anniversary of this issue? 😆

Didn't happen, let's hope for the 15 years anniversary!

@TekExplorer
Copy link

TekExplorer commented Dec 15, 2023

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?

@lrhn
Copy link
Member

lrhn commented Dec 15, 2023

@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: #1605 (comment), 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request Requests to resolve a particular developer problem
Projects
None yet
Development

No branches or pull requests