pyecore/pyecoregen

Derived collection generation

Closed this issue · 3 comments

I'm currently adding the support for derived collections in PyEcore, which means that using pyecoregen (with some adjustments), it will be possible to have a dedicated Python UML implementation. The thing with the derived collection is that it will obviously requires to manually add code in order to implement their behaviors.

In the case of a simple generation (no mixins), there is no issue, the code could be generated in the module along with the EClass code and that's all.

However, I'm not sure where to insert the code for the derived collection in the case where the user code is generated in a mixin. For example, for UML superClass reference, which is a derived collection, each time a Class is added to the superClass collection, a Generalization must be created at the same time. A first naïve code would look like this:

class DerivedSuperClass(EDerivedCollection):
    # ... code for __len__ ...etc
    def insert(self, index, item):
        self.check(item)
        self.owner.generalization.append(Generalization(general=item))

    # ...

If the derived code is located with the mixins, it cannot make a import Generalization as Generalization is in uml and uml imports mixins. A way of 'breaking' the circular dependency could be to add the import directly in the method that needs it, but I'm not sure that would be the best solution. I find a little bit cumbersome to ask people of importing like this.

I'll look into this tomorrow and see whether I have some ideas.

Let me see whether I got this right. Implementing derived properties is always done in user code, as the model cannot express the derivation rules, so the generator is at a loss what to do. This is true for simple one-valued attributes as is for collections. Do you agree?

Now you are saying: Fine, I as a user will then add the derivation logic where it belongs, namely into the mixin class. However, there I would need to access generated classes and hence we get the cyclic import. Correct so far?

Now, what to do about this...

There is no way to prevent the generated code to import the mixin module, otherwise we cannot derive from those classes. In fact we should probably work on the mixin side. It is a common concept that mixins are typically very loosely coupled to their using classes, which means they just call into methods and attributes of the using class blindly, without importing.

In our case this could be done by replacing the explicit call to Generalization(...) with a call of some method implemented in the class derived from the mixin, a factory method in this case. I don't have the exact class relations in front of my mind's eye, but candidates would be a create_generalization(), a create_base_item() or something similar. In very nasty cases, a string would be required to specify the concrete type as in create_item('Generalization').

I agree that the local import is a bit annoying. However, it is very straight forward, while the factory method approach is more elegant, but more magic as well.

What do you think? Did I address the right question in the first place? 🤣

Yep, you totally address my question, thanks a lot for your time Mike! Your answer gave me some good reflexion angle as well. Exactly, derived collection are "user code" as it cannot be derived by the generator (at least, not that I'm aware of). From the two solutions you propose, you're right, the factory is more eleguant (didn't thought about this one), but the local import is more direct. Regarding the point you developed about the factory, perhaps it would be better to keep the "straight forward" approache and stick to the local import at the moment. I didn't thought about it, but there is no way of knowing if a derived property will create extra elements or not, and there is a great chance that many derived properties will only "refine" collections or compute stuffs without creating anything. Consequently, it sounds more reasonible to "allow" local import once in a while instead of generating a huge factory and extra mechanism for cases that will rarely occur.

With this in mind, I will propose you a derived collection generation soon (as soon as I properly release it in PyEcore). Thanks again Mike!