Patterns of translating to patterns (part II)

After the introductory blog posts on the importance of modeling, the concept of model queries (with OCL and EMF-IncQuery) and model evolution, I demonstrated the translation of OCL expressions into graph patterns. Continuing from last time, I present patterns that can be applied to translate OCL expressions to the query language of EMF-IncQuery.

From the collection operations, we have previously covered select(), reject(), and collect(). Let us now see another set of the collection operations included in the OCL Standard Library, as defined by the OCL standard. OCL supports the usual binary set operations such as union, intersection, set difference, symmetric difference, as well as equality and the inclusion or exclusion of elements. Their translation to graph patterns is very straightforward.

Union and inclusion, difference and exclusion

Union and inclusion can be handled by the 'or' language element of EMF-IncQuery.

some_set->union(other_set) 

and

some_set->including(other_element) 

will both translate to graph patterns like this:

{
TRANSLATED<some_set>;
} or {
TRANSLATED<other_set>;
}

Let us apply this on the following OCL definition:

context SchoolClass def:
  namesInClass(): Set(EString) = 
   students.name->including(homeroomTeacher.name)

The result of the translation will be:

pattern namesInClass(self: SchoolClass, x:EString) = {
  SchoolClass.students.name(self, x);
} or {
  SchoolClass.homeroomTeacher.name(self, x);
}

Difference and exclusion, on the other hand, require negative pattern composition. The OCL definition below:

context SchoolClass def:
  nonHomeroomTeacher(): Set(Teacher) = 
   courses.teacher->excluding(homeroomTeacher)

will translate to the following (shown in simplified syntax, support for this syntactic sugar not implemented yet):

pattern nonHomeroomTeacher(self: SchoolClass, x: Teacher) = {
  SchoolClass.courses.teacher(self, x);
  neg {
    SchoolClass.homeroomTeacher(self, x);
  }
}

Intersection, product, emptyness and other operations

Cartesian product is very straightforward:

some_set->product(other_set) 

simply translates to:

  TRANSLATED<some_set>(x);
  TRANSLATED<other_set>(y);

as constraints in a graph pattern body are conjunctive. For the very same reason, intersection is also handled the same way:

some_set->intersect(other_set) 

will translate to:

  TRANSLATED<some_set>(x);
  TRANSLATED<other_set>(x);

Note that the only difference is that the two translated constraints are expressed on the same variables in this case.

Emptyness checking can be simply achieved by excluding the pattern variables from the pattern parameter list:

context SchoolClass def:
  hasNameConflicts(): Boolean =  
    self->nameConflicts()->notEmpty()

becomes

pattern hasNameConflicts(self: SchoolClass /* the rest are not parameters */) = {
  find nameConflicts(self, _anyStudent1, _anyStudent2, _anyName);
}

Derived set operations such as symmetric difference, (in)equality, subset / membership checks can be expressed using the previously discussed patterns of translation.

Accumulating ("reduce" / "fold" / "aggregate") operators

Aggregation operations condense a whole set into a single value. This includes size(), which counts the elements of a set, as well as the self-explanatory sum(), min() and max(). These operations can be mapped to aggregation operations in EMF-IncQuery (currenlty only counting is implemented). For example:

self.courses->sum(weight)

would be equivalent with the following constraint:

sum(x) find helper_pattern(self, x);

where the helper pattern is defined as follows:

pattern helper_pattern(self: ScholClass, x: EInt) = {
  SchoolClass.courses(self, y); // translated from the set expression
  Course.weight(y,x); // translated from the summed up expression 
}

More generally, one could argue that select(), collect(), etc., as discussed in the previous post, also belong in this family of operations. The most general case is iterate(), however, it cannot be always expressed by graph patterns, as it is very heavily dependent on ordering.

One final related operation is closure(), the iterated-to-fixpoint version of collect(), commonly used to compute transitive closures. The transitive closure language element of EMF-IncQuery can be used in this case as a translation.

Further collection operations

Collections of collections can be flattened to a single collection of elements using OCL's flatten(). In graph patterns, this happens automatically, so no corresponding code has to be generated. On the contrary, "relationification" and the graph pattern translation might not be possible at all unless such multi-level OCL collections are flattened.

Speaking of which, mathematical relations and thus graph patterns have inherent set semantics. OCL collections (Bag, OrderedSet, List), created e.g. by applying the operation sortedBy(), are therefore untranslatable with the exception of Sets. Although in some cases, it is enough to consider the support set (i.e. the set of all contained elements, regardless of ordering and multiplicity) of such collection data structures, as it may be enough to evaluate a large portion of operations. For instance,

some_list->select(those_with_some_property)->containsAll(some_other_elements) 

is equivalent to

some_list->asSet()->select(those_with_some_property)->containsAll(some_other_elements) 

which is translatable to graph pattern; this simplification is in fact crucial to the applicability of the whole approach, since multi-valued references in EMF are actually ordered sets. As counter-examples, there are collection operations such as count(), which is the number of times an element is included in the collection, as well as e.g. equality; these have significantly different semantics in the various collection types, and are therefore translateable in case of true OCL Sets only.

Acknowledgement: this research was realized in the frames of TÁMOP 4.2.4. A/1-11-1-2012-0001 „National Excellence Program – Elaborating and operating an inland student and researcher personal support system”. The project was subsidized by the European Union and co-financed by the European Social Fund.