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 Traces and Invocations"

(Invocation classes)
(Trace statuses)
Line 178: Line 178:
  
 
===Trace statuses===
 
===Trace statuses===
 +
 +
The trace status indicates how the invocation progresses
 +
* true - successful execution, all predicates matched
 +
* false - unsuccessful execution, some predicate failed
 +
* null - execution in progress
 +
* non-existent
 +
 +
The trace of R(x:X, y:Y) is non-existent for any pair of objects {x,y} in which one or both objects fail to conform to {X,Y}; there is no point creating traces for every possible/semi-relevant tuple of bindings. Consequently a QVTr override predicate
 +
 +
not R(x,y)
 +
 +
when reified in QVTc as
 +
 +
x.TR.invocationStatus = false
 +
 +
is too simple since navigation from the absence of TR is presumably invalid and remains invalid after navigation and comparison. A true result is needed for a non-existent trace. Perhaps
 +
 +
x.TR = null or x.TR.invocationStatus = false
 +
 +
But while the x.TR.invocationStatus access will wait for an invocationStatus assignment and so avoid the null for execution in progress, the x.TR will also wait for an assignment. This requires all impossible traces to be assigned as null hen first considered. But if the traces are narrowly types we just have to replicate the type matching predicate:
 +
 +
not x.oclIsKindOf(X) or x.oclAsType(X).TR.invocationStatus = false
  
 
==Bound Variables==
 
==Bound Variables==

Revision as of 04:13, 26 August 2017

The QVT specification of QVTc trace class synthesis for QVTr transformations is very thin. This document considers the real requirements.

This working document takes over from

QVTr specification oversights

Non-top Relations

QVTc has no support for non-top mappings consequently a non-trivial synthesis of an X_Y mapping is suggested to accommodate the Y relation after X. This scales quadratically when X or Y has overrides.

Better to synthesize an invocation class produced by the caller and consumed by the callee. To accommodate overriding a hierarchy of invocation classes and interfaces is needed.

Overrides

QVTc has no support for overriding and so overriding must be synthesized during the QVTr2QVTc transformation.

A simple synthesis requires two actions for a top relation and three actions for a non-top relation.

  • caller-side, invocation of a non-top relation must trigger the callee by creating an invocation request instance
  • callee-side, each overridden relation has an AND of when-not-predicate on each of the transitively overriding relations
  • caller-side, each call of an override has an OR of all possible overriding relations
    • the OR can be realized by a polymorphic invocation/trace class

Statuses

A when/where predicate may involve complex boolean expressions involving the value of many RelationCallExp instances.

This can be accommodated if the trace class includes a status property denoting success/fail/not-ready. The run-time execution must ensure that a fail status is assigned implicitly; success can be assigned explicitly.

Implicit when's

RelationCallExp's in when predicates are synchronous

  • invoker starts
  • invoker initiates when calls
  • invoker waits for when returns
  • invoker resumes

RelationCallExp's elsewhere in Boolean expressions such as

where { if A(x,y) then B(x,y) else C(x,y) endif; }

make a synchronous (implicit-when) call of A(x,y).

Not a particular problem once the assumption of trivial predicates is removed.

CollectionTemplateExp traces

QVTr2QVTc omits CollectionTemplates. It is therefore unclear whether the collection and/or members and rest should be traced.

A fundamental requirement is that the trace should facilitate an incremental re-execution.

The members and rest can be deduced from the overall collection or vice-versa. So there might be a free design choice.

However anonymous members/rest are only interesting wrt existence rather than value, so members/rest offers significant space saving opportunities.

Temporarily the overall collection is more consistent with ObjectTemplateExp tracing.

Trace Packaging

The QVTr specification defines the basic naming and typing of trace Classes and Properties. It ignores Packages, Models and URIs.

Packages, Models and URIs are also a bit troublesome for QVTr/QVTc too. Eclipse QVTr therefore simplifies a Transformation to be just a Class and to require a conventional Model/Package hierarchy ancestry.

Trace Property opposites

OCL's opposites are fundamental to the reification of traceability by QVTc trace classes. However opposites are ignored by the trace class synthesis.

The opposite naming is trivial.

The opposite multiplicity requires careful consideration of whether the non-opposite occurs below a 1:N navigation.

For endogeneous transformations, a further complication arises with domain identification.

The Eclipse QVTr trace therefore has Ecore EAnnotations to fully characterize the opposites.

Eclipse QVTr solutions

The 'simple' approach of transforming overrides to an AND of not-predicates on polymorphic overrides:

  • incurs quadratic costs in the QVTc exposition - optimizable by QVTi
  • does not distinguish invoked/execution traces
  • requires an OR of all invoked results
    • unless the trace classes exhibit polymorphism
  • is vague about handling of no override match
    • unless a further none-of-the-above mapping handles the no-match case
  • is vague about handling of multiple override matches
    • a static analysis problem

Polymorphic overrides once extended to non-top invocations requires polymorphic invocation classes as well as polymorphic trace classes

The synthesized trace comprises

  • a Model
  • a Package per (typically one) Transformation
  • a trace Class per Relation/Mapping
  • a trace Property per traced Variable
  • a status Property for tested progress
  • an invocation Class per non-top directly invoked Relation
  • trace/invocation interface Classes for overriding Relation/Mapping
  • infrastructure Classes

The Package URI is configurable.

Package, Class, Property names are derived from Transformation, Relation, Variable names.

Invocation classes

Each directly invoked non-top not-overridden relation has an invocation class to reify its invocation in QVTc.

The invocation class extends AbstractInvocation and is contained by the invoking trace class.

Each invocation of a non-top not-overridden relation realizes an instance of the invocation class.

Each root variable is bound by a property in the invocation class.

For when invocations a further initially null property is assigned the trace class instance after successful/failed execution.

Execution occurs when the invoked (top) mapping responds to the invocation class instance and assigns its trace to the invocation.

Invocation interfaces

Each non-top overridden relation has an invocation class and an invocation interface to reify its invocation in QVTc.

The invocation interface inheritance follows the overrides hierarchy with AbstractInvocation at the root.

Each invocation class extends its corresponding invocation interface.

Each invocation of a non-top overridden relation realizes an instance of the invocation class.

Each root variable is bound by a property in the invocation class.

The properties are typed and hosted by the interface of the least overridden relation to which they apply. Pattern matching within the overriding relation checks/converts the overridden types to the overriding types.

For when invocations a further initially null property is assigned the trace class instance after successful/failed execution.

Execution occurs when the invoked (top) mapping responds to the invocation class instance and assigns its trace to the invocation. The derived invocation class selects only those overrides of the invoked relation. The invoked mapping matches its interface in order to consider only viable overrides. Internal not-predicates inhibit execution when an override succeeded.

Invocation statuses

Invocation statuses are only needed for explicit and implicit when invocations. The invocation class property is assigned to the trace of the mapping that executed.

Ambiguity, if multiple overrides succeed, is the status successful for all/any? Is the possibility of a multiple success a static error?

Each overridden relation requires a not-predicate on all the overriding relations incurring a quadratic reification cost in QVTc that we would like to avoid at run-time. The overrides form a tree (in QVTr but more generally a singly rooted acyclic directed graph). The tree nodes can be labelled breadth first with the label maintained in the AbstractInvocation to track progress. The Connection dispatcher treats the tree as a state machine for the search for the matching override. This can be an efficient implementation, but we cannot easily express it in QVTc; leave it for the QVTi connection synthesis to discover it.

Trace classes

Each not-overridden relation has a trace class to reify the execution of the corresponding mapping.

The trace class has property for each 'bound' variable; separate issue; the status necessary fir incremental execution.

Invocation occurs when a matching pattern of input objects binds to the root variable, one of which is an invocation class instance for a non-top invocation.

Invocation creates the trace class instance.

If the invocation is a when invocation, an additional status property is assigned true on successful execution, or false after an unsuccessful (predicate failed) execution. The when-invoker may use this status.

Trace interfaces

Each overridden relation has a trace class and interface to reify the execution of the corresponding mapping.

The trace interface inheritance repeats the overrides hierarchy with AbstractTrace at the root.

Each trace class extends its corresponding trace interface.

Each invocation (root variables match) of an overridden relation creates an instance of the invocation class.

For when invocations a further initially null property is assigned true/false after successful/failed execution.

The concrete trace class is created by the executing mapping. The interface is accessed by the caller primarily to test success, but possibly to test the enforced result. The status is provided by AbstractTrace. The non-root variables can be accurately typed in the leaf concrete class.

The root variables could be accurately typed in each leaf concrete class with a hierarchy of accurately typed get-operations to support access from the interface.

Alternatively the root-variables could be provided with least overridden type in the least overridden interface. No operations needed, but some run-time type discovery/checking may be needed.

Design decision: all trace properties (other than the status) in the concrete trace class. Hierarchy of get-operations for the root variables in the trace interfaces.

(In UML, perhaps derived/redefined properties might work directly, but in Ecore we need the get-operations.)

Trace statuses

The trace status indicates how the invocation progresses

  • true - successful execution, all predicates matched
  • false - unsuccessful execution, some predicate failed
  • null - execution in progress
  • non-existent

The trace of R(x:X, y:Y) is non-existent for any pair of objects {x,y} in which one or both objects fail to conform to {X,Y}; there is no point creating traces for every possible/semi-relevant tuple of bindings. Consequently a QVTr override predicate

not R(x,y)

when reified in QVTc as

x.TR.invocationStatus = false

is too simple since navigation from the absence of TR is presumably invalid and remains invalid after navigation and comparison. A true result is needed for a non-existent trace. Perhaps

x.TR = null or x.TR.invocationStatus = false

But while the x.TR.invocationStatus access will wait for an invocationStatus assignment and so avoid the null for execution in progress, the x.TR will also wait for an assignment. This requires all impossible traces to be assigned as null hen first considered. But if the traces are narrowly types we just have to replicate the type matching predicate:

not x.oclIsKindOf(X) or x.oclAsType(X).TR.invocationStatus = false

Bound Variables

QVTr's definition of bound variables is vague, particularly for Collections.

Hidden variables

Template patterns are nominally single property descent, but initializers may be arbitrary expressions such as a.b.c. If we only trace a, then chnages to a.b or a.b.c escape incremental re-evaluation. If the trace is QVTs-derived rather than QVTr-derived this hazard could be avoided. Possibly even for a.b(c).

Collection variables

Consider tracing the OrderedSet{first ++ _} pattern against a simple Family::children : OrderedSet(Person). For the family {a,b,c}.

The residue is irrelevant so the single trace of {a} is adequate to detect a change in a. Addition of {a,b,c,d} is irrelevant.

But tracing Set{child ++ _} against Family::children : Set(Person) now has three traces {a}, {b}, {c} and a further trace is needed if d is added.

Eclipse QVTr rejected solutions

Dispatcher approach

It is tempting to introduce a dispatcher mapping to mediate between

  • a simple call of the dispatcher replacing the overrides
  • many simple calls from the dispatcher to the selected override

overrides => Dispatcher Relation

Each invocation of an overridden relation X is synthesized as an invocation of DispatchX. DispatchX is a synthesized relation that orchestrates the direct invocations of each override. DispatchX has a distinct invoked trace where the overall status return is supervised. X and its overrides are each invoked individually and so have normal executed traces and statuses.

The synthesized dispatcher relation provides opportunities for optimizations by hoisting shared predicates into the dispatcber.

non-top => invoker-created trace

A non-top relation requires an invoker to support QVTc's top-only semantics. With override polymorphism handled by a Dsipatcher Relation allnon-top invocations are direct so the creation of the trace class can be moved to the invoker.

Lifecycle:

  • invoker creates partially populated trace class and if synchronous waits for the trace class status to be assigned
  • invoked relation executes and assigns status and other properties
  • invoker resumes accessing and acesses result properties

Overall

No need for trace interfaces. No need for invocation classes.

The synthesized trace comprises

  • a Model
  • a Package per (typically one) Transformation
  • a trace Class per Relation/Mapping and per Dispatcher
  • a trace Property per traced Variable
  • a status Property for tested progress
  • infrastructure Classes

The Package URI is configurable.

Package, Class, Property names are derived from Transformation, Relation, Variable names.

Oops

Unfortunately neither QVTm nor QVTi support conditionalization of the required realize/new and so a single mapping override dispatcher is impossible. Introducing a if-statement could rescue this approach.

Back to the top