Open eclipse-qvtd-bot opened 1 week ago
By Ed Willink on Feb 21, 2017 15:55
Consider a real world example:\ abstract class Child { name : String }\ class Boy extends Child;\ class Girl extends Child;\ key Child{name};\ mapping newBoy(name) => Boy{name};\ mapping newGirl(name) => Girl{name};\ the key enforces the sensible practice that all children in a family have distinct names. But the example is nonsense. Following newGirl("alex"), newBoy("alex") returns the previous girl; tears and lawsuits in the maternity department. The key is a good practice, not a necessity.
Consider another example where uniqueness may be genuine.\ abstract class NationalDocument{ id : String }\ class DrivingLicense extends NationalDocument{ name : String };\ class Passport extends NationalDocument{ name : String };\ key NationalDocument{id};\ mapping newDrivingLicense(name,id) => DrivingLicense{name,id};\ mapping newPassport(name,id) => Passport{name,id};\ but there will just be chaos if the DrivingLicense::id and Passport::id are not orthogonal. The key could be justified, but only with an associated discipline. Two keys would be more sensible.
"key A(b,c)" used by "a = :A{b=b,c=c}" is syntax sugar for something like "when { KeyA(b,c,a); }" used by "a = a"
the expansion defines no polymorphism, but could be elaborated for more interesting requirements.
Conclusion: keys do not apply to derived types and so cannot be used for abstract types.
Conversely, if they did, many uses of a derived key class would have to diagnose a type hazard that the re-used object might be type incompatible.
By Ed Willink on Feb 26, 2017 01:14
(In reply to Ed Willink from comment #1)
Conclusion: keys do not apply to derived types and so cannot be used for abstract types.
The ModelMorg AbstractToConcrete example (Bug 512734) uses a key on an abstract type in a way that is clearly intended to apply to derived types. The example is hard without a derived semantic.
Solution: a source code key K(a,b) is a short form for a distinct DerivedK(a,b) for each non-abstract classes that derive from K. Does this apply to unknown K's from extended metamodels?
If the source code key K(a,b) implies a run-time uniqueness of the tuple {K,a,b} then extended metamodels are not a problem.
By Ed Willink on Feb 26, 2017 01:21
(In reply to Ed Willink from comment #2)
a run-time uniqueness of the tuple {K,a,b}
No. For an endogeneous transformation a key cannot enforce uniqueness across two models. The uniqueness must be {TM,K,a,b} to isolate TypedModels.
By Ed Willink on Feb 26, 2017 01:25
(In reply to Ed Willink from comment #3)
The uniqueness must be {TM,K,a,b}
Clarifying, the unique Tuple is:\ TypedModel - in which the unique object is 'transitively contained'\ K - the actual class of the unique object,which is a derived declared key class\ a,b,... - the actual values bound to the key parts
By Ed Willink on Feb 26, 2017 04:02
Keys are for exact actual type so for AbstractToConcrete example that relates Class to Class via the Type, the result is always a Class, even if the input is a DerivedClass; there is no justification for the arbitrary re-use of the actual source type.
If derived keys are implicit then multiply-derived keys are implicit too. The derived keys do not need to be individually implemented, since it is not possible to use an ancestral key to re-use a derived type. Rather the multiply-derived keys create an implicitly merged key; all parts of each ancestral key must be provided in the source code, which is validatable since the multiply-derived type is used by the source.
For singly derived keys, it should be sufficient to just maintain the {Key-class, part-a, part-b} tuple with distinct Key-class for each implicitly derived key. For multiply derived, the merged part-list will require a new 'Key' to define the new tuple. Each type has exactly 0 or 1 keys.
By Ed Willink on Feb 26, 2017 05:15
(In reply to Ed Willink from comment #4)
the unique Tuple is: TypedModel - in which the unique object is 'transitively contained' K - the actual class of the unique object,which is a derived declared key class a,b,... - the actual values bound to the key parts
If we implement this way, the underlying ShadowExp needs to acquire two extra parts with run-time support.
Since we have to synthesize multiply-derived keys anyway, why not synthesize singly-derived ones two.
The TypedModel parameter is only needed for the case of an endogeneous transformation with multiple enforced domains; generate extra functions for this rare case.
By Ed Willink on Feb 26, 2017 06:43
Gotcha. If derived keys are implicitly created, it is impossible to have not-keyed derived classes.
Is a no-part Key declaration a not-keyed declaration? How does multiple inheritance of keyed + not-keyed work?
For small transformations, it is probably good practice to enumerate all keys explicitly, so implicit keys could be considered bad.
For large transformations, which is more painful:
When evolving a large transformation, which is more painful
There is no perfect solution.
Perhaps:
Add the "key KeyClass{}" semantics for not-keyed.
Add a WFR warning for enforcement of a derived key class without an explicit derived key/not-key.
Add a WFR warning for an explicit not-keyed that does not silence a warning about an derived key.
By Ed Willink on Feb 26, 2017 07:02
Perhaps a new derived keyword so that "derived key ..." specifies the derived key hierarchy automatically.
By Ed Willink on Mar 03, 2017 08:40
A closely related problem. What happens to properties of a keyed class that are not part of the key? In particular the container. The current implementation sets them to null.
Problem: if the other properties can be assigned in the surrounding context, the keyed object changes, and these changes are shared with other users. Oops, other users may share the object and also define its other properties. If this affects the container, we have child stealing.
Conclusion:
'exclusive' - non-Collection. We can allow a keyed object to accumulate children.
By Ed Willink on Mar 03, 2017 09:28
(In reply to Ed Willink from comment #9)
Conclusion:
- a key's parts must include the containing property.
Implementing this WFR causes most JUnit tests that have a key to fail. Disturbing.
A) Some of the tests are just lazily written - not a problem.
B) Some are clearly suspect such as in RelToCore
key emof::Class{name};
Why should MyPackage::Time be unified with YourPackage::Time? It seems that the WFR is diagnosing a genuine bug.
C) What if there really is a requirement to make MyType{name} unique, as is the case for the Pivot orphanage(s) for Collection specializations?
Presumably MyType{name} will form part of a model so it will have a container. If the container is anarchic or rather just first come first served, we have a lack of determinism.
For the Pivot orphanage, there is a central orphanage for active use that disperses to local orphanages during serialization. It is not anarchic.
Unique keyed objects with uncertain containment seem really smelly.
D) Can keyed objects be used as root variables without knowing the container?
If the container is determinate, it may not be so hard to specify, but still a nuisance.
Suppose we treat the current parts as 'explicit' parts and add e.g the container as an 'implicit' part that must be discoverable in the creation context, and checkable in a lookup context. Creation can work as is, but what if the lookup detects a container conflict?
Obvious use case.
Given a key MyType{name}, clearly name is a unique identifier so one might expect to be able to do a name=>Type lookup, and from there a to-container lookup.
But if there is no instance there can be no container to lookup.
Seems inescapable. Keys must involve a container and no mutation. Keys cannot be root variables.
Combined with limited derivation, keys are looking very very broken.
By Ed Willink on Mar 03, 2017 09:36
But what is the real use case?
A key provides a lazy way to magically re-use an object in one place that was created in another place without the hassle of creating a unifying construction relation and referencing it in a when clause.
If reification of the key requires the tooling to synthesize the unifying construction relation and references to it, we can perhaps be more generous about allowing half the initialization in one place and the rest in another. But reporting errors on synthetic code will be distinctly opaque to users.
(The current tooling synthesizes a construction function whose return is cached wrt its arguments. Pretty similar but apart from demonstrating limitations it doesn't help the diagnosis.)
By Ed Willink on Mar 03, 2017 10:31
(In reply to Ed Willink from comment #9)
- a keyed object in an enforced domain may not enforce any other exclusive key properties
'exclusive' - non-Collection. We can allow a keyed object to accumulate children.
Conversely, every property with a non-zero lowerbound must be initialized on construction, which pretty much requires it to be a key part.
If there are many such parts with degenerate values, the degenerate values must be specified at every key reference site. In contrast a when predicate references a relation that can encapsulate the degenerate value computation.
?? Is the utility of some simple use cases sufficient to justify retaining this broken language facility ??
?? can when predicates evolve to accommodate the simple key use cases ??
By Ed Willink on Mar 03, 2017 10:48
It is not clear that it is worth upgrading QVTrelation.ocl with all the WFRs for such a broken language facility, and then repairing the ripples to the JUnit tests.
One 'tested' Key WFR that breaks current tests is:
inv PartsIncludesContainer:\ part.opposite?->exists(isComposite) or oppositePart->exists(isComposite)
For now, treat keys as deprecated. See how well we get on without them. See if a good motivation appears.
By Ed Willink on Mar 03, 2017 12:55
Steve Wartik asks: What about a property for which isID is true? Does the semantics of QVT need to account for those properties?
Mm. In UML 2.5 the specification of isID is very weak, "may", "can" and so consequently unuseable in non-proprietary fashion. The probable behaviour is that one or more properties uniquely identify a Classifier, but what about inheritance?
I see no real difference between a UML 2.5 isId and the QVTr Key MyType{myPropertyWithIsID}. We have all the same problems of type compatibility.
Potentially the UML property
MyType::myPropertyWithIsID : idType
supports a global model query operation such as
MyType::getElement(id : idType) : MyType
How this might be used in an application is application specific so the application is responsible for coherent usage. Typically this may be used to locate an input model element efficiently.
QVTr appears to provide an automated usage that shares an output element. This turns out to be far from coherent.
By Ed Willink on Apr 23, 2017 06:48
(In reply to Ed Willink from comment #0)
[Resolved elsewhere: there cannot be overlapping partial keys: A(b,c)+A(b,d) => A(b,c,d)]
email to Steve Wartik.
The prevailing code had (still has) a WFR prohibiting multiple keys for the same type. This diagnosed a violation in the UML2OWL
key UMLMetamodel::Property {name, class};\ key UMLMetamodel::Property {name, association};
The violation is fixed by a change to
key UMLMetamodel::Property {name, class, association};
for which in practice association/class is null so the original keys were actually unique since the omitted property was null giving no duplicates in practice. The single key has no duplicates in principle.
By Ed Willink on May 05, 2017 04:07
email correspondence with Sreedhar provides a minimal principle for Key semantics. Ultimately a Key is just an output model WFR. But the transformation must exploit it since any failure to unify by key leads to output WFR failures.
Once seen as an output WFR, we can see that the unscoped Keys in RelToCore are just wrong. We can also see that repeating Keys for derived classes is unwise, but not wrong; the most derived wins but if not consistent or unique we have a cannot-transform failure. See Bug 516223 for consideration of other WFRs.
(A Key cannot be trivially converted to a Relation since a Key has only one domain; its instantiation in actual rules must be identified to extract the creation signature, or we just use the current QVTr2QVTc conversion to a necessarily unique Function.)
By Ed Willink on May 05, 2017 04:21
(In reply to Ed Willink from comment #16)
if not consistent or unique we have a cannot-transform failure.
This must be captured by a WFR (warning).
By Ed Willink on May 10, 2017 06:31
(In reply to Ed Willink from comment #0)
Derived keys must be orthogonal to avoid duplicate base instances.
Prescient but vague.
(In reply to Ed Willink from comment #17)
(In reply to Ed Willink from comment #16)
if not consistent or unique we have a cannot-transform failure.
This must be captured by a WFR (warning).
Keys apply to derived types too, but multiple keys for any given type must have no overlap in their properties.
By Ed Willink on May 10, 2017 08:12
(In reply to Ed Willink from comment #17)
(In reply to Ed Willink from comment #16)
if not consistent or unique we have a cannot-transform failure.
This must be captured by a WFR (warning).
Not rigrously possible. If we have
Base{x}\ Derived1{y} -- Derived1 extends Base\ Derived2{z} -- Derived2 extends Base
and
Relation1 creates a Derived1 from an x and a y\ Relation2 creates a Derived2 from an x and a z
we cannot tell whether the possibility that a lack of uniqueness wrt Base{x} is a problem that must lead to a cannot-transform failure or is inherently safe as a consequence of user model subtleties.
Maybe a warning whenever a derived key instance is created.
By Ed Willink on May 10, 2017 08:20
(In reply to Ed Willink from comment #19)
Relation1 creates a Derived1 from an x and a y Relation2 creates a Derived2 from an x and a z
Worse if
Relation1 creates a Derived1 from a y\ Relation2 creates a Derived2 from a z
with an x being defined by a subsequent where relation, we cannot tell when creating Derived1/Derived2 whether there is a Base key violation or not.
Either the subsequent assignment of x must detect the delayed violation
Or creation of an instance must specif/default all properties of all keys.
By Ed Willink on Feb 09, 2018 06:12
Summarizing comment added to http://issues.omg.org/browse/QVT14-50
Rather than requiring that all non-collection, non-default, non-derived properties are enumerated as Key parts, we could just automatically 'add' them, so that the enumerated parts are a subset of the mandatory parts that the transformation author promises are an adequate discriminator. The user improves run-time efficiency by reducing the key dimensionality as a consequence of fewer parts.
But consider RelToCore's Class{name}.
Omission of Class::isAbstract is an efficiency since no way will there be distinct abstract and non-abstract versions of the same named class. If multiple versions are called for then we need a run-time error pointing at the transformation coding error.
Omission of the owning Class::package is a bug, since same-named classes in different packages should be permitted.
Possible pragmatism:
Or even, omitted explicit container is automatically remedied by an implicit key part.
But if the user really did want globally unique classes, providing a helpful implicit package owner part, 'helpfully' hides the ambiguous functionality (which package owns the globally unique classes)
No. omitted container key part is a compile time (WFR) failure
By Ed Willink on Feb 10, 2018 07:17
(In reply to Ed Willink from comment #21)
But consider RelToCore's Class{name}.
...
Omission of the owning Class::package is a bug
Attempting to impose discipline on the keys in ModelMorf's Seq2Stm we find
key StmcMM::Transition{name,owner,fromState,toState}; ...\ enforce domain stm tr1:Transition { toState = ... }; ...\ when { ... MessageToTransition(m1, tr1); ... }
3 out of four parts were assigned in a when invocation in which the fourth property has a non-trivial object and a fifth assignment.\ The fourth is re-assigned above.
[checking with the pre-beta4 download, these observations seem to apply to the ModelMorf original; not a consequence of Eclipse debugging.]
Re-assignment of a key part cannot be right; it changes the identity.
Removing the key declaration appears to make no difference.
Is the gratuitous declaration a nice reassurance to the programmer, or an outright violation of a plausible discipline?
If as in the example a keyed object is returned by a when invocation and then changed, how does that interact with another when invocation that returns the same keyed object and makes a different change? Does a change to a keyed object, preserve the original and create a clone for the change? If the change makes the new object the same as a different keyed object are the two keyed objects unified? Do references have to track all the evolving identities?
This seems horribly complicated and not very declarative.
Overall, the Seq2Stm keys can be used as output WFRs and should all pass.
If we try to restructure Seq2Stm, the two when calls to MessageToTransition could be inlined into MessageSequenceToTransitionSequence exposing the double assignment to Transition::toState. The conflict could be resolved by prioritizing the outer assignment.
Moved to Bug 530991.
For now:
==> All keyed object must be created in an ObjectTemplateExp that specifies all its part values; container may be deduced from the bound variable container.
==> No changes may be made to any non-collection keyed object slot
==> Changes to collection keyed object slots may only accumulate
By Ed Willink on Jun 22, 2019 03:51
Bug 548536 identifies the possible utility of a suite of SetType/BagType/SequenceType/OrderedSetType keys for the Ecore type to Pivot type mapping.
By Ed Willink on Jun 29, 2019 03:21
Bug 548757 identifies that the prevailing implementation mechanism for keys
A Key_XXXX query to enforce the unqiueness wrt the arguments
should be re-used to support the uniqueness of non-top relation invocations.
(This implementation mechanism enforces the Key-is-an-output-postcondition semantics. The declarative limitations of Keys should be diagnosed by WFRs.)
| --- | --- | | Bugzilla Link | 512532 | | Status | NEW | | Importance | P3 normal | | Reported | Feb 21, 2017 14:52 EDT | | Modified | Jun 29, 2019 03:21 EDT | | Blocks | 512734, 515236 | | See also | 516223, 530991, 548536, 548757 | | Reporter | Ed Willink |
Description
The spec is vague on how inheritance and keys interact.
Consider the A(b,c) key.
If we need to 'create' a derived-A(b,c) when an A(b,c) exists, A may not be a derived-A, so the A(b,c) key cannot apply to derived-A. But surely it should? If we create a derived-A(b,c) then we have two A(b,c). Is it an error?
If we need to 'create' an A(b,c) and a derived-A(b,c) exists, do we use the derived-A?
??? No its normal. If we want a static A, then we use only the static A key, for which a derived-A may be available, but we only use a static A so the derivation is fine. Creating an A key instance may also create a super-A key instance.
But we don't specify that we want a static A; we specify that we want a derived-A and the A-uniqueness may deliver a different derived-A.
What are the WFRs relating derived-A(b,c,d) and A(b,c)? Surely a pure sub-set so that some A(b,c) are also derived-A(b,c,d). Vice-versa, no derived-A(b,c,d) is not a A(b,c).
Derived keys must be orthogonal to avoid duplicate base instances.
Can A be abstract? Yes.
[Resolved elsewhere: there cannot be overlapping partial keys: A(b,c)+A(b,d) => A(b,c,d)]