Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "QVTd In-place and Copy Transformations"

(Copy versus In-Place transformation)
(Mechanisms)
 
Line 59: Line 59:
 
=Mechanisms=
 
=Mechanisms=
  
Operations and Relations provide type-dependent functionality. Just about anyting can be done with an OPeration, but it isn'y bidirectional. Therefore try to solve problem using Relations/Mappings.  
+
Operations and Relations provide type-dependent functionality. Just about anything can be done with an Operation, but it isn't bidirectional. Therefore try to solve problem using Relations/Mappings.  
  
 
==Copy invocation==
 
==Copy invocation==

Latest revision as of 13:16, 22 March 2017

The QVT specification has vague indications that QVTr / QVTc supports in-place transformations but provides no real details.

Copy transformations in which the output is very similar to the input are not considered at all.

This working document considers how copy and in-place might be supported. It takes over from

Use Cases

Deep Copy (The Primary Use Case)

Support an EcoreUtil.Copier-like capability:

  • deep copy of an input model
  • arbitrary input classes, some of which may use an extension metamodel not known until run-time
  • references to copied class instances are redirected to the copies
  • references to external class instances reference the unchanged external class instance

QVTr solution

Impossible, since there is no ability to create a dynamically determined type.

Even if all classes are known at programming time, it is necessary to enumerate an explicit copy of each class. See the

-- copy an ocl expression
-- For space reasons this relation is not expanded out here

at the end of the RelToCore example.

QVTc solution

Impossible, since there is no ability to create a dynamically determined type.

Endogeneous Transformation

An endogeneous (out-of-place) transformation may create a slightly modified copy of its input.

Shallow Copy / Clone

It would seem that shallow copy or clone should be possible. But these are ill-defined. How is a composition feature shallow copied?

In practice languages that offer clone / shallow copy require per-type customization so that a form of copy that satisfies user aesthetics is implemented. This can be achieved by a hierarchy of overrides. No need for any special support.

This is rather similar although there is much more adjustment of a pure copy, types are usually known. There may be many copy invocations.

Copy versus In-Place transformation

These could be treated as separate challenges, but from a programmer perspective they are identical; a potentially late run-time option can specify whether the output overlays or replicates the input. We therefore treat copy and in-place as the same problem when considering UMLX, QVTr, QVTc. The distinction may perhaps occur as alternate qvts2qvti code synthesises, or perhaps just a variant clone/re-use in the qvti run-time.

Copy versus non-copy transformation

A non-copy transformation specifies all created types explicitly. Output slots are populated explicitly.

A copy transformation can re-use the source type/object as the target type/object. Output slots may be populated implicitly (by copy/re-use).

Using a non-copy language semantics for a copy requires all slots to be enumerated to participate in the copy. This is verbose and error prone.

An alternative copy language semantics need only specify deviations from a perfect deep copy. cf. Henshin's create/delete markups.

Mechanisms

Operations and Relations provide type-dependent functionality. Just about anything can be done with an Operation, but it isn't bidirectional. Therefore try to solve problem using Relations/Mappings.

Copy invocation

In QVTr, a when { DoCopy(a,b}; } could invoke a copy between a and b.

In QVTc, the conventional QVTr2QVTc conversion of the QVTr invocation should work.

=> regular

Base Copy Type definition

A normal invocation implies a normal definition that happens to deep copy. The 'create' action needs to be annotated as a copy.

For an individual copied type within a pattern of not-copies, the node in a QVTr pattern perhaps needs a 'copy' prefix.

For an endogeneous transformation we would like to avoid duplicate programming. Perhaps a form of differential relation in the source syntax can be syntax sugar for numerous 'copy' prefixes.

This is circular a when annotates an assign as a copyCall, which has a body that copies. Which is more primitive? annotation of an assignment or annotation of a domain. In QVTc, we annotate a 'realize'. QVTr hides the ralize, so it is not annotateable, ? annotating a domain makes sense?

=> irregularity: created type is defined by source object rather than program declaration

Property Copy definition

A custom copy may provide variant property assignments. Ideally the defaults property copies would magically not happen. This is very hard if the copy is distributed over many collaborating relations. Therefore a practical restriction performs all local property assignments then all implicit assignments. Users cannot distribute copy functionality.

=> irregularity: explicit property assignments followed by residual implicit copied property assignments => restriction: copy assignments must be local to a copy relation

Derived Copy definition

If a copy is a relation, a derived relation supports a derived copy. Regular.

=> restriction: trace class inheritance must persist the relation derivations.

Trace Class

If a copy is a relation, it has a trace class. Regular.

Details

QVTc / QVTr copy semantics

QVTr is realized by a QVTr2QVTc transformation, so it is really the QVTc copy semantics that we must resolve.

Derived types

QVTc cannot create/re-use a derived type. A variant realize, a typeof operator or a template type could re-use the dynamic type, but we must also deep copy all the slots that are not assigned explicitly. Is a copy except a list of properties needed? Is it possible to perform a global transformation analysis to identify the except properties automatically? Is it necessary to defer to a run-time capability that automatically deep copies what isn't explicitly assigned? How would a run-time capability know to perform the late deep copies if multiple mappings collaborate in the assignments?

A copy-except-list-of-properties makes the problem a programmer responsibility, so it can be right. A WFR can detect some conflicts between the except-list and the actual assignments. This is similar to a WFR that can detect missing assignments.

The copy-except can be defined more positively as a CopySignature, rather like a QVTr Key; the name of a copied base-type and the names of the properties that are to be assigned explicitly, rather than copied automatically. realize xInstance:XClass using XCopySignature.

Copy Trace Classes

A copy needs a simple 'trace' comprising XCopySignature{in:X; out:X} allowing navigation such as 'myIn.XCopySignature.out'.

How does the copy trace relate to the mapping trace?

For a simple mapping that creates a copy the mapping trace could inherit/aggregate the copy trace.

For a more complex mapping that create more than one copy, the corresponding multiple inheritance is only an option under normal OO semantics with careful renaming, unfortunately EMF always imposes diamond inheritance. Multiple aggregation is possible. Without inheritance, does a copied object have separate navigations using the mapping trace class and the copy trace class?

Derived copy-trace classes

One CopyTrace declaration may extend another CopyTrace declaration to extend the number of not-deep-copied properties. It has no effect on the copied class which is always the same as the source.

Incremental Copy

If an obscure property in a derived copied class is changed, then an incremental copy must re-copy. How does a trace that is unaware of this property handle it? Possibly any mutation of the source triggers a full copy. Possibly the trace has an 'anything-else' so that known properties are handled efficiently, only unknown properties get the anything-else treatment.

Multiple Copy Signatures

What if a class has two distinct refined copy mappings?

Each should have a distinct copy-signature and so there should be two possible navigations.

What if a multiply inherited class inherits multiple copy mappings?

Each should have a distinct copy-signature and so there should be multiple possible navigations. The base copy traces may navigate to distinct objects. The more derived copy traces may (redundantly) navigate to the same object.

Normal multiple inheritance and casting could allow shared references, but EMF won't support it so we will need the bloat of all possibilities.

Internal/External references

A direct implementation of a reference copy must make a two-way decision as to whether the mapping navigates through the trace object of the copied internal source object, or just re-uses the external source object. Provided the trace object navigation support a Boolean test capability for existence this test is possible but messy.

We cannot eliminate the trace of the internal, but we can add a copy-trace for the external thereby ensuring that all references always use a copy-trace, even if the external trace is degenerate with identical source/target objects; just as an in-place might also have. This makes the QVTc much more regular and so simplifies the QVTr2QVTc synthesis.

QVTr copy syntax

ModelMorf

ModelMorf introduces a replace keyword that may qualify a relation to support in-place. Unfortunately I do not understand the description in the Readme.htm of its Beta-4. Perhaps there is some similarity with the domain declaration proposed below.

Transformation declaration

Applying a magic copy keyword to the transformation declaration could change the semantics/syntax but how might a transformation that needs a bit of copy and a bit of non-copy select between semantics. Seems too coarse grained.

Relation declaration

Applying a magic copy keyword to the relation declaration is more flexible but limiting for examples such as the TTC PetriNet2Statechart that need to trans form a pair of domains in-place.

Relation inheritance

Copy semantics could be inherited if default copy relations are auto-generated and available for re-use by derived relations. Unfortunately QVTr overrides is a replace rather than augment capability, so this would need changing. It is not clear how this handles types not known at programming time.

Absolute Domain declaration

Applying a magic copy keyword to the domain declaration creates a problem that there are then pairs of domains with the same name. The intuitive WFR could be relaxed.

Relative Domain declaration

A copies xxxx clause could suffix a domain declaration to indicate that the new domain is based on a copy of the xxxx domain.

Within the copy domain we can exploit the multi-rooted capability to require that all root-variables of the copy domain have names that appear in the copied domain. The copy domain therefore defines one or more Henshin-like overlay patterns; a pattern for creation, an equals null for deletion.

A very similar effect could be achieved by adding a mutation { ... } clause to the copied domain. This is slightly shorter and avoids a duplicated name, but adds to the brace syntax complexity of domains, wheres and collections.

QVTc copy syntax

QVTs implications

Copy

A realized node may be

  • created with the specified type (current functionality)
  • created/re-used with a referenced type with a selective deep copy

The new usage adds a copy-trace middle element in addition to the mapping-trace middle element. The type of the copy-trace corresponds to a copy signature and so a list of not-deep-copied properties.

In-place

We need extra edges to enforce the write-after-read re-use.

We probably don't need extra caching since the middle model caches anyway.

QVTi implications

ModelMorf Examples

AbstractToConcrete

Taking over from https://bugs.eclipse.org/bugs/show_bug.cgi?id=512734

transformation AbstractToConcrete(uml1:UMLMM; uml2:UMLMM) {
	key UMLMM::Type {name};
	key UMLMM::Operation {name, class};
	key UMLMM::Parameter {name, operation, type};

	top relation AbstractClassToConcreteClass {
		t:UMLMM::Type;
		acon, acopn:String;

		domain uml1 cc:Class {
			inheritsFrom = ac:Class {
				isAbstract = true,
				operation = aco:Operation {
					name = acon,
					parameter = acop:Parameter {
						name = acopn,
						type = t
					}
				}
			}
		};

		enforce domain uml2 cc:Class {
			operation = cco:Operation {
				name = acon,
				parameter = ccop:Parameter  {
					name = acopn,
					type = t
				}
			}
		};
	}
}

At first sight we have an unusual re-use of cc requiring per-domain namespaces. There is no correlation of cc's name. This allows many outputs per input unless the keys do something magical.

In-place

But once we read this as in-place it's quite simple.

The input is a four deep iteration: cc,ac,aco,acop

The output re-uses cc, but far from being a problem this uses the differential semantics re-proposed above. The "cc" re-use locates an input location at which a fully enumerated clone is created.

The keys should therefore be a gratuitous irrelevance which is good since they are semantically unsound.

The failure to re-specify cc.name is irrelevant because it's in-place differential re-use.

BUT. There is no very obvious declaration that identifies the use of differential in-place semantics. Is the consequence of re-use of a variable name too obscure/hazardous? Adding a "copies uml1" to the uml2 domain declaration seems to be a major aid to readability and WFR power.

Copy

What is needed to allow the in-place definition to also be a copy definition?

a) we need an 'Element' copies 'Element' non-top relation that implicitly deep copies.

b) we need a root 'dummy' copies 'dummy' top relation that overrides 'Element' copies 'Element' and so implicitly deep copies the 'dummy' root.

c) we need the existing class tweak top relation to be a non-top relation that overrides 'Element' copies 'Element' and so refines the class copy.

Possible but a slightly unpleasant amount of boilerplate.

If "uml2 copies uml1" appears as part of the transformation declaration,

a) we can synthesize an 'X' copies 'X' non-top relation for each inheritance-root metamodel element

b) a 'Y' copies 'Y' top relation for each possible containment-root metamodel element

If "copy" rather than "top" qualifies the class tweaking relation

c) we can synthesize the override.

The above implies a primary family of deep-copying relations.

Nested Copy

The example also uses a nested copy, which it has to enumerate. This is actually a regular copy, so a when could do the copying.

		domain uml1 cc:Class {
			inheritsFrom = ac:Class {
				isAbstract = true,
				operation = aco
			}
		};
		enforce domain uml2 cc:Class {
			operation = cco
		};
                when {
                    copy(aco, cco);
                }

This should be able to work in-place too. The in-place non-copy exploits a genuine copy.

New QVTr solution

transformation AbstractToConcrete(uml1:UMLMM; uml2 copies uml1)
{
--	relation AbstractToConcrete{   -- auto-generated
--		domain uml1 e:Element{};
--		enforce domain uml2 copies uml1 e:Element{};
--	}

--	top relation AbstractToConcrete{   -- auto-generated
--		domain uml1 d:dummy{};
--		enforce domain uml2 copies uml1 d:dummy{};
--	}
	
	relation AbstractToConcrete{
		domain uml1 cc:Class {
			inheritsFrom = ac:Class {
				isAbstract = true,
				operation = aco
			}
		};
		enforce domain uml2 copies uml1 cc:Class {
			operation = cco
		};
	        when {
                    AbstractToConcrete(aco, cco);
                }
        }
}

The commented inheritance-root and containment-root relations are auto-generated as a consequence of the "uml2 copies uml1" in the transformation declaration.

The default copy re-uses the transformation name. The variant copies again re-use the name; they are not overrides. They are name re-uses for appropriate dispatch during deep copy.

New QVTc solution

The abstract TAbstractToConcrete_copy

abstract class TAbstractToConcrete_copy { e1 : OclElement[1], e2 : OclElement[1], c1: OclElement[?], c2 : OclElement[?]};
class TAbstractToConcrete_copy_dummy extends TAbstractToConcrete_copy {};
class TAbstractToConcrete_copy_Element extends TAbstractToConcrete_copy {};
class TAbstractToConcrete_copy_Class extends TAbstractToConcrete_copy {};

transformation AbstractToConcrete {
    uml1 imports UMLMM;
    uml2 imports UMLMM;
    imports ABS2CONC;
}

-- The top copy for the dummy containment root
map AbstractToConcrete_copy_dummy in AbstractToConcrete {
    uml1(d1:dummy | ) {}
    enforce uml2()
    { copy d2 := d1; }  -- realize type-of(d1), assign copies to all attributes not assigned explicitly in this mapping
     where () {
        realize trace : TAbstractToConcrete_copy_dummy |
        trace.e1 := d1;
        trace.e2 := d2;
        trace.c1 := null;
        trace.c2 := null;
    }
}

-- A copy for the a typical child element
map AbstractToConcrete_copy_Element in AbstractToConcrete {
    uml1(e1:Element,c1:Container| c1 = e1.oclContainer()) {}
    enforce uml2(c2:Container | c2.TAbstractToConcrete_copy.c1 = c1)
    { copy e2 in c2 := e1; }
    where () {
        realize trace : TAbstractToConcrete_copy_Element |
        trace.e1 := e1;
        trace.e2 := e2;
        trace.c1 := c1;
        trace.c2 := c2;
    }
}

-- Replacement copy for the a Class element
map AbstractToConcrete_copy_Class in AbstractToConcrete {
    uml1(e1:Class, c1:Container, ac:Class| c1 = e1.oclContainer(); e1.inheritsFrom = ac; ac.isAbstract;) {}
    enforce uml2(c2:Class| c2.TAbstractToConcrete_copy.c1 = c1)
    { copy e2 in c2 ::= e1; }
    where () {
        realize trace : TAbstractToConcrete_copy_Class |
        trace.e1 := e1;
        trace.e2 := e2;
        trace.c1 := c1;
        trace.c2 := c2;
        trace.ac := ac;
    }
}

-- When copy action for the a Class Operation
map AbstractToConcrete_copy_Operation_when_AbstractToConcrete_copy_Class in AbstractToConcrete {
    uml1(e1:Operation, c1:Class| c1 = e1.oclContainer();) {}
    enforce uml2(c2:Class| c2.TAbstractToConcrete_copy_Class.c1 = c1)
    { copy e2 in c2 ::= e1; }
    where () {
        realize trace : TAbstractToConcrete_copy_Operation_when_AbstractToConcrete_copy_Class |
        trace.e1 := e1;
        trace.e2 := e2;
        trace.c1 := c1;
        trace.c2 := c2;
    }
}

Solution elements

QVTr

copy family

A copy family comprises one or more same-named, same-domained relations to configure a deep copy.

A copy family member typically has two domains, one input and one output, however other inputs and outputs can be consistent and may be required by PetriNet2StateCharts.

The input domain has a single uniquely typed root variable. Each output domain typically has a single similarly typed root variable.

A default copy family is specified by a "copies" in the transformation declaration.

Further copy families may be defined by prefixing a relation with the copy keyword.

copy transformation

A copy transformation (a copies keyword separate target and source domains in the model parameters list) causes a default copy family to re-use the transformation name. Unless explicitly defined, a copy relation is synthesized for each inheritance-root and each containment root.

copy relation

A copy relation (a copy keyword prefixes the relation keyword) defines a root of a deep-copy rel.

The name of the relation is reserved for use by the copy family.

The output domains specify that they copy their input domain.

copy invocation

The default copy is invoked implicitly for the roots of the input model by virtue of being a top relation.

Copy relations may be explicitly invoked by when/where clauses.

QVTc

copy newChild in newParent := oldChild

This variant of realize seems to be all that is required.

It creates/re-uses a newChild using the type=of oldChild.

It installs newChild's containment by newParent replicating the containment of oldChild.

propagate the copy of oldChild's slots to newChild

This is hard. It needs reflection to copy all the derived class slots. It must not copy explicitly assigned slots.

Expressing this in QVTc where there is no support for eager call, just comprehensive predicate guarded matching, would require a per-slot mapping each with its own guard so that the explicit per-slot mapping occludes the reflective per-slot mapping. Nasty and still needs reflection.

Easier just to leave it as magic until QVTs2QVTi where the explicit copy is easily coded, the explicit assignments can be performed before a reflective traversal can omit the explicitly assigned slots.

QVTu...QVTs

Just need QVTc's magic copy rather than realize creator.

QVTi

Copy creator creates a new same-typed object. Explicit property assignments occur. Explicit call/schedule of copy mappings for implicit child properties.

NB copy mappings are probably speculated to create objects prior to reference resolution.

Use Case Solution

QVTr

copy-key MyTypeKey{isCopy};
copy-key MyDerivedTypeKey extends MyTypeKey{isCopy2}

relation UseACopy {
    domain model1
        c1 : MyContext {
            t1 : MyType {}
        }
    domain model2
        c2 : MyContext {
            t2 : MyType {}
        }
    when {
        MakeABaseCopy{t1, t2);
    }
}   
 
relation MakeABaseCopy {
    domain model1
         t : MyType {};
    domain model2 copies model1 using MyTypeKey
         t { isCopy = true; };
}    
 
relation MakeADerivedCopy overrides MakeABaseCopy {
    domain model1
         t : MyDerivedType {};
    domain model2 copies model1 using MyDerivedTypeKey
         t { isCopy = true; isCopy2 = true; };
}    

QVTc

mapping UseACopy {
    domain model1(t1 : MyType){}
    domain model2(t2 : MyType | t1.TMakeABaseCopy.out = t2){
}   
 
mapping MakeABaseCopy {
    domain model1(t : MyType){};
    domain model2 copies model1
         realize t2 := ;
}    
 
mapping MakeADerivedCopy overrides MakeABaseCopy {
    domain model1(t : MyDerivedType){};
    domain model2 copies model1
         t { name = t.name.toLower(); };
}    

Back to the top