dart-lang / language

Design of the Dart language
Other
2.61k stars 200 forks source link

mixins with implicit call #3944

Closed tgpsantos closed 18 hours ago

tgpsantos commented 6 days ago

Hi guys, first time here. This might not be the first time that someone's asking for this, but unfortunately couldn't fiund anything in previous issues.

Going directly to my use case

I have several mixins I use in my app. Their methods are intended to be used as listeners, so when I add them to a class, I need to register those classes in a list.

mixin A {
    void listenerFunctionA() { }
}

mixin B {
    void listenerFunctionB() { }
}

mixin C {
    void listenerFunctionC() { }
}

(... imagine several other mixins)

Then I add them to the classes

class MyClassWithA with Mixin A {
}

class MyClassWithBC with Mixin B, C {

}

final objectA = MyClassWithA()
final objectBC = MyClassWithBC()

I have a list that I use to register them, so later I can broadcast the actions to the corresponding listeners

List myListMixinA = []
List myListMixinB = []
List myListMixinC = []

And then finally comes the step I'd like to avoid, which is to add them to their corresponding list of listeners:

listenersA.add(objectA)
listenersB.add(objectBC)
listenersC.add(objectBC)

I'm trying to find solutions that could automatically register the objects in their listeners just by adding the mixin clause to the class.

Right now the best option I'm aware and is closer to this is to use inheritance and register the listeners in the parent abstract class, but that forces the abstract class to have all the potential mixins, which results in an excessive amount of calls to objects / functions I don't need. For instance, I wouldn't like to be calling objectA with the functions from mixin B,C. There's an alternatively with inheritance which would be to have an abstract class for each possible mixin combination, but that would be unsustainable.

So in essence I'd like to find a solution that could help me register these objects automatically, like forcing all the these instantiated objects (objectA, objectBC) to pass through a particular function for each of their mixins. Is this something feasible at the moment?

lrhn commented 4 days ago

so when I add them to a class, I need to register those classes in a list

Do you need to register the classes in a list, or be able to register instances of them in a list.

The examples code suggests that you have, somewhere, a number of lists corresponding to the individual mixin types. You want to be able to add an object to all the lists it applies to. Preferably without having to have individual code for each mixin.

First of all, I think it's unlikely that this is a candidate for a language feature. It seems too specialized. Unless there is some more generally useful language feature it can be created from, it's more likely to be something fixed using library code. Such a feature could be "link-time maps" (https://github.com/dart-lang/language/issues/371).

Otherwise it's tricky. Nothing happens automatically, you would have to write some code. (This may be a job for macros when they are ready.)

The mixins are independent and doesn't know about each other, and do not implement some shared super-type. That makes it impossible to use a single function, like addToList(List someList) that calls super.addToList if the list doesn't fit the current mixin, because the first mixin won't have a super.addToList to call. (On the other hand, if there is some method that all mixins implement, and which can call its super-implementation, so the mixins can only be applied to objects with a base implementation of that memeber, then there are several options for doing something by calling that method with one or more lists.)

Letting each mixin have its own method for adding would mean you have to call all those, then you might as well just do

if (element is MixinA) myListMixinA.add(element);

repeatedly.

I'd probably just have a class for the listener, rather than a simple list, so you could do:

final myMixinAListeners = Listeners<MixinA>();
...
  myMixinAListeners..tryAdd(element); // does `if (element is MixinA) this.list.add(element);`.

Then you can have a list of all the lists, and try adding each value to each listener. (Quadratic time initialization, obviously.)

tgpsantos commented 3 days ago

so when I add them to a class, I need to register those classes in a list

Do you need to register the classes in a list, or be able to register instances of them in a list.

my bad, I meant instances as you correctly assumed below

The examples code suggests that you have, somewhere, a number of lists corresponding to the individual mixin types. You want to be able to add an object to all the lists it applies to. Preferably without having to have individual code for each mixin.

First of all, I think it's unlikely that this is a candidate for a language feature. It seems too specialized. Unless there is some more generally useful language feature it can be created from, it's more likely to be something fixed using library code. Such a feature could be "link-time maps" (#371).

Otherwise it's tricky. Nothing happens automatically, you would have to write some code. (This may be a job for macros when they are ready.)

The mixins are independent and doesn't know about each other, and do not implement some shared super-type. That makes it impossible to use a single function, like addToList(List someList) that calls super.addToList if the list doesn't fit the current mixin, because the first mixin won't have a super.addToList to call. (On the other hand, if there is some method that all mixins implement, and which can call its super-implementation, so the mixins can only be applied to objects with a base implementation of that memeber, then there are several options for doing something by calling that method with one or more lists.)

Letting each mixin have its own method for adding would mean you have to call all those, then you might as well just do

if (element is MixinA) myListMixinA.add(element);

repeatedly.

I'd probably just have a class for the listener, rather than a simple list, so you could do:

final myMixinAListeners = Listeners<MixinA>();
...
  myMixinAListeners..tryAdd(element); // does `if (element is MixinA) this.list.add(element);`.

Then you can have a list of all the lists, and try adding each value to each listener. (Quadratic time initialization, obviously.)

I understand. Doing that it's how I eventually solved the problem, but as you pointed out it has to be done repeatedly, which is slightly inconvenient and a bit error-prone in this use case.

I understand that a mixin is independent from the other classes, but since it's possible to force a class / list to be overriden in a mixin, I wondered whether there could be something like an onInit() method that could be overridden (the same way as a super()) in order to be able to do some logic in there.


Just to be clear if I understood correctly, something like the following snippet is not possible within the dart language, correct?

mixin A {
    List myListMixinA;
    void listenerFunctionA() { }

    @override
    void onInit() {
        // called when the object instantiates 
        myListMixinA.add(this)
        super.onInit()
    }
}

Appreciated for your thoughtful answer. Also apologies, in hindsight I think I overexplained my use case in the initial post when I could've summed it up with something like the previous snippet.

lrhn commented 3 days ago

There is no onInit fuctionality in Dart classes other than constructors. Giving mixins constructors is an option, but not a small feature (can't find an issue for it, but there should be one).

You can execute code when an object is initialized by putting it into a field initializer: void _dummy = _staticCode();, but the code cannot access the object being initialized, so that won't help. It's an constructor with a body that can be run right after object creation.

If you can make all your objects extend the same base class, then you could do:

class BaseObject { 
  BaseObject() {
    afterConstruct();
  }
  @mustCallSuper
  void afterConstruct() {}
}

and then every mixin would be:

mixin A on BaseObject {
  // ...
  @override 
  void afterConstruct() {
    myListMixinA.add(this);
    super.afterConstruct();
  }
}

Not awesome, but it's within the bounds of what you can do yourself today.

tgpsantos commented 18 hours ago

That would be complex for this use case since they are already extending their own different abstracts. But thanks for the suggestion and answers.

I'll close this issue now