Bidirectional References

Hi,

in EMF it is possible to make bidirectional references by setting their "opposite". For example, if A has a reference b to B ( A -b-> B ) and B -a-> A one can say that "a" and "b" are opposites and the underlying infrastructure will take care of maintaining the objects (e.g, if I have objects a->b and b->a and I set a new object b2 to a, I will get a->b2, b2->a, b->null). This also works if one of the sides has cardinality 0..1 and the other 0..n, which gives the same effect as children (many children, one parent) but without the containment property.

I don't think MPS has this concept of bidirectional references, does it?

In any case, I am trying to simmulate it with what MPS offers. Can you give a hint of the best way to approach it? Things I tried:

- using constraint language's referent set handler to manipulate the references. That works pretty well because I can manually manage the references when they change. The problem is that the set handler does not get called when I remove the target (in an editor) or set it to null. For example, if I have an editor for A and I delete the reference to B the set handler does not get called with null.

- creating new types BRef and ARef. This way I can use the Delete editor action to trigger the reference management code when the BRef gets deleted in an A editor. But then the code gets really ugly because everywhere I have this extra level of indirection.

- using TypeSytem NonTypeSystemRules and QuickFixes. The idea is that I can always check if the reference is correct and, if not, I can automatically trigger the quickfix. My problem with this is that it 1) it's not really clear when the rules will run and 2) if there are many of those rules it starts to impact performance because essentially every time you open the editor you have to check every reference.

Thanks for the help!

Thiago

-- edited --


PS - I know that you can't make 0-n references in MPS, but I never understood why. So if I want to have the effect of (many children, one parent), I need that the "many" side have specialized (smart) references as children...

4 comments
Comment actions Permalink

Hi Thiago,

I don't think MPS has this concept of bidirectional references, does it?

you are right, i had the same problem before. See the discussion here

- using constraint language's referent set handler to manipulate the references. That works pretty well because I can manually manage the references when they change. The problem is that the set handler does not get called when I remove the target (in an editor) or set it to null. For example, if I have an editor for A and I delete the reference to B the set handler does not get called with null.

actually it is the way you describe ist. maybe it will be changed in the future (a call with null wouldn't help you here anyway, you would need the old node leading to a more complex interface (parameters!) here).

- using TypeSytem NonTypeSystemRules and QuickFixes. The idea is that I can always check if the reference is correct and, if not, I can automatically trigger the quickfix. My problem with this is that it 1) it's not really clear when the rules will run and 2) if there are many of those rules it starts to impact performance because essentially every time you open the editor you have to check every reference.

for performance-reason (as you explain) i wouldn't do that.

I'd use smart-references (Ref-Objects with a 1-card-ref; what is a good way to handle references anyway) to solve your problem. This is similar to what you explain in your second option... just create the reference-concept (you have even tool-support for that and can create a smart-reference (incl. the delegating editor) from within any AbstractConceptDeclaration-node via Intention!) and derive the TYPE from the referenced concept (so you introduce no new type into your model).

PS - I know that you can't make 0-n references in MPS, but I never understood why. So if I want to have the effect of (many children, one parent), I need that the "many" side have specialized (smart) references as children...

I think the reason for that is simply KISS and the smart-reference... :-)

Regards,

Mirko

0
Comment actions Permalink

Hi Mirko,

thanks for the answers!

mirko wrote:


- using constraint language's referent set handler to manipulate the references. That works pretty well because I can manually manage the references when they change. The problem is that the set handler does not get called when I remove the target (in an editor) or set it to null. For example, if I have an editor for A and I delete the reference to B the set handler does not get called with null.

actually it is the way you describe ist. maybe it will be changed in the future (a call with null wouldn't help you here anyway, you would need the old node leading to a more complex interface (parameters!) here).


I think that they already support that. The referent handler receives the old and new referent:

referent set handler:(referenceNode, oldReferentNode, newReferentNode, scope)->void {
  System.out.println("Moving from " + oldReferentNode + " to " + newReferentNode);
}

If I have a reference from A->B and I change the referent B object, this code is called. Then I could simply add myself to the back list of this object. The problem is that this function is not called when I simply remove the B object from the reference. I'd like to receive a call with oldReferentNode set to b and newReferentNode set to null.

In fact, it seems to be a bug: http://twitter.com/markusvoelter/status/12748418992578560

mirko wrote:


- using TypeSytem NonTypeSystemRules and QuickFixes. The idea is that I can always check if the reference is correct and, if not, I can automatically trigger the quickfix. My problem with this is that it 1) it's not really clear when the rules will run and 2) if there are many of those rules it starts to impact performance because essentially every time you open the editor you have to check every reference.

for performance-reason (as you explain) i wouldn't do that.

I'd use smart-references (Ref-Objects with a 1-card-ref; what is a good way to handle references anyway) to solve your problem. This is similar to what you explain in your second option... just create the reference-concept (you have even tool-support for that and can create a smart-reference (incl. the delegating editor) from within any AbstractConceptDeclaration-node via Intention!) and derive the TYPE from the referenced concept (so you introduce no new type into your model).

Cool trick with the Intention, I didn't know that!

Setting the type helps with the solutions using my language, but my language code still has this extra indirection level. Instead of having A->B I have A->BRef->B and everywhere in my language code I have to make a.bref.b when I mean a.b. It would be great if this indirection level were understood at language level, but I don't think it's possible.

So, my current solution to this problem is to provide only A->B as a single reference, and calculate the list of B->A on demand. If that referent is called with null, I could make this explicit.

BTW, I tried creating a delete action for BRef, but again, it's not properly called.

Cheers!

Thiago

0
Comment actions Permalink

Hi Thiago,

I think that they already support that. The referent handler receives the old and new referent:

referent set handler:(referenceNode, oldReferentNode, newReferentNode, scope)->void {
  System.out.println("Moving from " + oldReferentNode + " to " + newReferentNode);
}

ah, ok... didn't realize that... so it actually looks like a bug. did you file one?

Setting the type helps with the solutions using my language, but my language code still has this extra indirection level. Instead of having A->B I have A->BRef->B and everywhere in my language code I have to make a.bref.b when I mean a.b. It would be great if this indirection level were understood at language level, but I don't think it's possible.

this indirection gives you also a bit more flexibility in cardinality (child vs. ref) and it is imho common practice in DSL-creation (for more flexibility and extensibility in structure/typesystem and the like). Another solution would it be to make the indirection "invisible" via some behavior (something like a.getB() instead of a.bref.b).

So, my current solution to this problem is to provide only A->B as a single reference, and calculate the list of B->A on demand. If that referent is called with null, I could make this explicit.

if "on demand" is suitable, this is a good solution, i think.

are you using findUsage-aspect for that?

Regards,

Mirko

0
Comment actions Permalink

Hi Mirko,

mirko wrote:

Hi Thiago,

I think that they already support that. The referent handler receives the old and new referent:

referent set handler:(referenceNode, oldReferentNode, newReferentNode, scope)->void {
  System.out.println("Moving from " + oldReferentNode + " to " + newReferentNode);
}

ah, ok... didn't realize that... so it actually looks like a bug. did you file one?

No, I didn't file a bug. Will do it soon.

Setting the type helps with the solutions using my language, but my language code still has this extra indirection level. Instead of having A->B I have A->BRef->B and everywhere in my language code I have to make a.bref.b when I mean a.b. It would be great if this indirection level were understood at language level, but I don't think it's possible.

this indirection gives you also a bit more flexibility in cardinality (child vs. ref) and it is imho common practice in DSL-creation (for more flexibility and extensibility in structure/typesystem and the like). Another solution would it be to make the indirection "invisible" via some behavior (something like a.getB() instead of a.bref.b).


Yeap, I am using this type of handler right now, I think it's the best solution.

So, my current solution to this problem is to provide only A->B as a single reference, and calculate the list of B->A on demand. If that referent is called with null, I could make this explicit.

if "on demand" is suitable, this is a good solution, i think.

are you using findUsage-aspect for that?

No, I am using "model.nodes(Type)" because the models I am editing are pretty small, so it's fine to traverse them fully (as I think this function does, I don't know if there's any indexing going on). Is findUsage faster/better?

Cheers!

Thiago

0

Please sign in to leave a comment.