Tuesday, July 02, 2013

FTL - A Plain Transformation Language for Ecore Models

Model transformations are a very important part of model driven software development approaches. However, popular transformation languages like QVT or ATL are not intuitive in their application and do neither provide a strong formal metamodel, nor a flexible transformation generator.

This is where Functional Transformation Language, or FTL for short, comes into play. It’s a functional model-to-model transformation language for Ecore models and is described itself in terms of an Ecore model and Eclipse Modeling Framework (EMF) tools like Xtext or Xtend.

Overview


Even though FTL is capable of transforming Ecore models, it does neither depend on the Ecore API, nor on Java or any other programming language. Instead, a FTL compiler translates FTL to an intermediate code and may perform some target language specific improvements e.g. tail call optimization. Currently, there is a reference implementation that compiles FTL to Java.
Let’s have a look at the basic concepts of FTL like the functional notion and the generation of Java code to perform the actual model-to-model transformation.

Functional Idiom

The best way to express a model transformation precisely is to describe it as a function that transforms model elements of the source metamodel to model elements of the target metamodel. This approach allows the specification of transformations with little syntactic overhead. Thus, FTL was strongly influenced by modern programming languages such as Scala, Xtend and Groovy. However, it provides some advantages in terms of model transformations compared to them. Especially, you just have to deal with the "what" and not with the "how".

Type System

FTL is a strongly typed language and supports only the most common data types natively. The compiler has to interpret the structural semantics of the expressions and attempts to infer its type. Since FTL uses a compiler in order to create the concrete code, every type that is supported by it can be used e.g. you could use an instance of java.util.List within the model transformation even if FTL is not aware of Java. In this case, the type has to be specified explicitly. For further information about mixing FTL with the target language, please refer to the section Interoperability.

Syntax


Every FTL transformation has to specify the root model element named "context" the transformation is performed on:

context farm.Farm;

Afterwards, there are a number of functions that take model elements as an input parameter and perform transformations on them. The following listing takes a Farm as an input parameter and milks every Animal that is a Cow and collects the resulting Buckets. In the end, Nothing is returned, which is a special value of type Nothing. It can be used whenever returning a value is not applicable due to some reason.

def milk(f: Farm): Nothing {
  for (a <- f.animals) {
  if (isCow(a)) {
  f.buckets += milk(a);
        }
  }
}

Note that isCow() is neither a FTL function, nor an operation of a model element. Instead, it is an invocation of a function available in the code of the language FTL is compiled to. In other words, it could be a Java method as follows:

public boolean isCow(Animal a) {
  return a instanceof Cow;
}

Functions can optionally be guarded by a condition enclosed by square brackets ("[]"). If it does not hold i.e. it evaluates to false, the function is not invoked.

def milk(cow: Cow) [cow.isFemale() && !cow.isMother()]: Bucket {
  if (cow.bio) {
  return milkByHand(cow);
  } else {
  return milkingMachine(cow);
  }
}

The following constructor function takes a Cow as an input parameter and creates a new Bucket instance (note the arrow => next to the return type) filled with the content of the Cow.

def milkByHand(cow: Cow): => Bucket {
  content = cow.content;
  cow.content = null;
}
// milkingMachine() has been omitted because we love our cows

Code Generation


As you can see in the previous listings, you do not have to deal with Ecore directly. The FTL compiler transforms FTL to ordinary code of the target language including the native function calls like isCow() from the example above. Besides that, the reference implementation for Java adds code for executing the transformation independently or as part of another application. You can see the corresponding Java source code below. Note that the code for running the transformation is omitted due to sake of simplicity.

package farm;

public class FarmTransformation {
  public static void main(String... args) {
  /* omitted the code for starting the transformation */
  }

  public void milk(Farm f) {
  for (Animal a : f.getAnimals()) {
        if (isCow(a)) {
           f.buckets.add(milk(a));
        }
     }
  }

  public Bucket milk(Cow cow) {
     if (!(cow.isFemale() && !cow.isMother())) {
        return null;
     }
     if (cow.isBio()) {
        return milkByHand(cow);
     } else {
        return milkingMachine(cow);
     }
  }

  public Bucket milkByHand(Cow cow) {
     Bucket bucket =
  FarmFactory.eINSTANCE.createBucket();
     bucket.setContent(cow.getContent());
     cow.setContent(null);
     return bucket;
  }
}

As you can see, the compiler performed a number of modifications on the FTL code. For example, all variables now have explicit type information, the guard condition of the milk() function became an if statement in the corresponding method and the constructor function that returned something of type => Bucket became a method that uses FarmFactory, which is provided by the metamodel, to construct Bucket instances.

Interoperability


As already mentioned, besides being a functional language supporting imperative constructs, FTL also supports data types of the target language as well as native function calls. This gives the opportunity to embed existing code into FTL and execute it as part of the model transformation.
Due to the fact that the compiler might not be aware of those types and functions during compile type e.g. because they can be loaded at runtime only, you have to give the compiler hints how they fit into FTL. The following listing contains a single function using java.util.List and java.lang.String:

def usageOfJavaTypes(list: java.util.List): String {
  val f: Farmer = f.get(0);
    val name: String = f.toString();
  return name;
}

Note that using types of the target language might reduce portability. If the FTL transformation would be compiled to C++, the compilation will fail unless the compiler replaces String with string and java.util.List with list. Furthermore, the native method calls such as get() and toString() are not available in C++ as well.
Nevertheless, this kind of extensibility bridges the gap between FTL and the target language and allows tailored model-to-model transformations along with other technologies. Therefore, this opportunity results in a clear benefit compared to other model transformation languages like ATL.

Getting FTL


FTL (including the Compiler and the IDE based on Eclipse) is published under the Eclipse Public License 1.0 and can be downloaded from Google Code:
https://code.google.com/p/functional-transformation-language/

No comments:

Post a Comment