|
|
(35 intermediate revisions by the same user not shown) |
Line 1: |
Line 1: |
− | {| border=0
| + | #REDIRECT [[OAW]] |
− | | width=90% |
| + | |
− | = Collection of features (rough) =
| + | |
− | | + | |
− | == Xtend ==
| + | |
− | | + | |
− | In Version 5 we want to improve some of the Xtend language concepts and features.
| + | |
− | Codename is Xtend++ :
| + | |
− | | + | |
− | === Imports ===
| + | |
− | The import mechanism should be reworked, so that every import is explicit.
| + | |
− | We won't need any metamodel configuration in the workflow nor in the editors anymore.
| + | |
− | This will not only make the setup simpler but will also improve the performance.
| + | |
− | | + | |
− | The syntax would change to something like the following:
| + | |
− | | + | |
− | import org:openarchitectureware:Extension; // native import
| + | |
− | import EMF "my/package/model.ecore"; // non-native import
| + | |
− | import UML "my/test.profile.uml" as profile; // non-native import with name space definition
| + | |
− | import Java "my.test.Class"; // non-native import introduces java types and features form my.test.Class
| + | |
− | import XSD "http://www.mywebsite.org/xsd/metamodel.xsd" // non-native import importing the types from an XSD file
| + | |
− | ... (think of Hibernate mapping files, Groovy, etc.)
| + | |
− | | + | |
− | ==== Native import ====
| + | |
− | A native import refers to another extension file imports all public members (types, functions, extensions).
| + | |
− | | + | |
− | ==== Non-native Import ====
| + | |
− | A non native import starts with an identifier pointing to an installed adapter.
| + | |
− | The adapter is responsible for loading and converting the type information from the given string. The syntax in the string is defined by the adapter. The token directly after the
| + | |
− | import keyword defines which adapter to use.
| + | |
− | | + | |
− | ==== Namespace definition ====
| + | |
− | All members are included without namespace information. If you need a namespace you can explicitely define one per import.
| + | |
− | | + | |
− | ==== Reexport ====
| + | |
− | The reexport keyword will be supported, so that
| + | |
− | imported stuff (types and functions) will be reexported.
| + | |
− | | + | |
− | Example:
| + | |
− | | + | |
− | myext1.ext
| + | |
− | | + | |
− | foo() : 'foo';
| + | |
− | | + | |
− | myext2.ext
| + | |
− | | + | |
− | reexported import myext1;
| + | |
− | bar() : 'bar';
| + | |
− | | + | |
− | client.ext
| + | |
− | | + | |
− | import myext2;
| + | |
− | fooBar() : foo()+bar();
| + | |
− | | + | |
− | === Generics ===
| + | |
− | We need full-fledged generics, which can conceptually be copied from Java's generics.
| + | |
− | | + | |
− | (complicated) Example:
| + | |
− | List<M> sort<T extends Comparable<T>, M>(List<M> toSort, (M)=>T closure)
| + | |
− |
| + | |
− | which can be used like this
| + | |
− | ['aa','b'].sort(e|e.size);
| + | |
− | | + | |
− | You don't have to deal with this complexity if you don't want to define functions which uses generics ;-)
| + | |
− | | + | |
− | === Closures ===
| + | |
− | We'll have real closures, not the built-in stuff we have now.
| + | |
− | Closure syntax:
| + | |
− | | + | |
− | { parameterList '|' expression-using-parameters-and-scope }
| + | |
− | | + | |
− | Where parameter list must be typed, either implicitly or explicitly.
| + | |
− | | + | |
− | Example:
| + | |
− | | + | |
− | {
| + | |
− | String myText := "test";
| + | |
− | (Attribute)=>Boolean myClosure := {e|e.name==myText}; // e is inferred from the declared type of the assignee
| + | |
− | myList.select(myClosure);
| + | |
− | }
| + | |
− | | + | |
− | alternatively declare the parameter types explicitly
| + | |
− | {
| + | |
− | var myText := "test";
| + | |
− | var myClosure := {Attribute e|e.name==myText};
| + | |
− | myList.select(myClosure);
| + | |
− | }
| + | |
− | | + | |
− | // type of e is inferred from the declaration of the 'select()' function, you don't have to use the curly brackets.
| + | |
− | myList.select(e|e.name == "test")
| + | |
− | | + | |
− | | + | |
− | | + | |
− | ==== Type signatures of functions ====
| + | |
− | The syntax for of a function's type signature looks as follows:
| + | |
− | | + | |
− | (parameterTypes)=>returnType
| + | |
− | | + | |
− | Examples:
| + | |
− | | + | |
− | ()=>Object
| + | |
− | (String)=>Boolean
| + | |
− | (String, Entity)=>Entity
| + | |
− | | + | |
− | Example 2: declaration of higher-order functions using generics :
| + | |
− | | + | |
− | List<T> select<T>(List<T> this, (T)=>Boolean closure) {
| + | |
− | ...
| + | |
− | }
| + | |
− | | + | |
− | === Functions ===
| + | |
− | | + | |
− | Functions can be invoked either using the functional syntax or using the member syntax (operation like, aka extensions):
| + | |
− | | + | |
− | myFunction(foo, bar) == foo.myFuntion(bar)
| + | |
− | | + | |
− | A function is declared as follows:
| + | |
− | | + | |
− | (private|cached|cached) ReturnType? functionName(declaredParameterList) ( guardExpression )? : bodyExpression ;
| + | |
− | | + | |
− | Example:
| + | |
− | private doStuff(Entity this) name!=null :
| + | |
− | name+"Stuff";
| + | |
− | | + | |
− | The detailed semantics of how the polymorphic resolution works (what role guards play here) and is described in the upcoming section.
| + | |
− | | + | |
− | or
| + | |
− | (private|cached) functionName(declaredParameterList) guardExpression blockExpression
| + | |
− | | + | |
− | Example:
| + | |
− | cached makeAbstract(Entity this) {
| + | |
− | abstract := true;
| + | |
− | name := 'Abstract'+name;
| + | |
− | this;
| + | |
− | }
| + | |
− | | + | |
− | Block expressions are explained in their own section.
| + | |
− | | + | |
− | ==== Polymorphic Resolution with signatures and guards ====
| + | |
− | Usually polymorphism is based on the types of parameters. The same applies for Xtend++.
| + | |
− | In contrast to e.g. Java we use the dynamic types (actual types at runtime) of a given set of parameters in order to find the function which
| + | |
− | best fits (has the most specific declared paramter types).
| + | |
− | | + | |
− | Example:
| + | |
− | given the following two functions
| + | |
− | | + | |
− | foo(String x) : "string";
| + | |
− | foo(Object o) : "object";
| + | |
− | | + | |
− | this assertions can be made:
| + | |
− | | + | |
− | foo('S') == "string"
| + | |
− | foo(34) == "object"
| + | |
− | foo((Object)'S') == "string" // would be "object" in Java
| + | |
− |
| + | |
− | In addition to these concept, commonly known as "multiple dispatch" or "multi method", we introduce guards which can be used to controll the resolution based on the state of a given object not only the type.
| + | |
− | | + | |
− | Example:
| + | |
− | | + | |
− | foo(String x) x.length>5 : "long";
| + | |
− | foo(String x) : "short";
| + | |
− |
| + | |
− | this assertions can be made:
| + | |
− | | + | |
− | foo('honolulu') == 'long'
| + | |
− | foo("bar") == 'short'
| + | |
− | | + | |
− | The semantics are as follows:
| + | |
− | | + | |
− | * First select all possible features (functions and operations), based on the name and the given parameter types.
| + | |
− | * Then order those features by parameter types (best match first).Those functions with the same parameter type should be order so that the ones having a guard defined come first.
| + | |
− | | + | |
− | pseudo code
| + | |
− | | + | |
− | for (Feature f : features) {
| + | |
− | if (f.hasGuard()) {
| + | |
− | if (f.guard.evaluate())
| + | |
− | return f; // return the feature where the guard evaluates to true
| + | |
− | } else {
| + | |
− | return f; // return the feature without a guard
| + | |
− | }
| + | |
− | }
| + | |
− | return null; // no invocation
| + | |
− | | + | |
− | * if there are features, but the guards evaluate to false, return null:
| + | |
− | | + | |
− | The static semantics are straight forward:
| + | |
− | * The guard must be of type boolean.
| + | |
− | | + | |
− | ====Extensions overwrite semantics====
| + | |
− | Functions and Operations can be overwritten.
| + | |
− | The precedence is based on th order of imports.
| + | |
− | Functions from later declared imports overwrite functions introduced before.
| + | |
− | Local functions overwrite imported functions.
| + | |
− | Consider overwriting the toString() Operation (which is invoked on String concatenations) for arbitrary meta types.
| + | |
− | This will allow very readable templates.
| + | |
− | | + | |
− | ==== dynamically scoped extension overwriting ====
| + | |
− | Another thing we want to address is the way one can extend generators provided by third parties (like the one shipped with GMF).
| + | |
− | So far everybody used AOP to "weave" customization and implementation in. The problem is that the generator designer does not really develop for extensibility and every Join Point becomes public API.
| + | |
− | We've been thinking about a concept called "dynamic extensions" which is a way to register extensions for a specific call graph.
| + | |
− | | + | |
− | Example:
| + | |
− | | + | |
− | with(&toString(Entity)) {
| + | |
− | callGMFCartridge(myEntity);
| + | |
− | }
| + | |
− | | + | |
− | Just to explain: '&toString(Entity) is a literal pointing to the 'toString(Entity)'-function. So one could write '&toString(Entity).evaluate(myEntity)' instead of 'myEntity.toString()' for example. The implementation of the function will be used whenever such a function is invoked within the callGMFCartridge(Entity) function (the third party cartridge).
| + | |
− | In other words one overwrites the toString() function for Entities for the following block.
| + | |
− | So what you as a generator developer could do is, provide a list of function which can be overwritten. In addition there is a final keyword, which prevents overwriting the corresponding function.
| + | |
− | | + | |
− | This won't be a replacement for AOP, but we think that the AOP feature has been missused in order to provide extensibility.
| + | |
− | In addition the extension emchanism was bound to the static context so far which really is a limitation in some situations.
| + | |
− | | + | |
− | === Code blocks ===
| + | |
− | | + | |
− | A code block is the replacement for chain expressions ( a-> b-> x) with the additional features:
| + | |
− | * provides variable declarations (Expression returning the assigned value)
| + | |
− | | + | |
− | It's something like a pseudo imperative syntax (but still is an expression!).
| + | |
− | | + | |
− | Variables are assign-once!
| + | |
− | | + | |
− | Example:
| + | |
− | | + | |
− | myExtension(String stuff) {
| + | |
− | def x := stuff.length();
| + | |
− | if x>56 then
| + | |
− | "Foo"
| + | |
− | else
| + | |
− | "Bar";
| + | |
− | endif
| + | |
− | }
| + | |
− | | + | |
− | A code block is itself an expression consisting of a list of expressions.
| + | |
− | It returns the value returned by the last expression.
| + | |
− | | + | |
− | It is possible to overwrite the scope.
| + | |
− | Example:
| + | |
− | | + | |
− | doStuff() {
| + | |
− | def x := "Foo";
| + | |
− | {
| + | |
− | def x:= "Bar";
| + | |
− | x;
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | will return "Bar"
| + | |
− | | + | |
− | === Object creation expression ===
| + | |
− | We are thinking about a syntax to create model graphs inline.
| + | |
− | We need this not only for model transformations but also for writing model transformation tests.
| + | |
− | | + | |
− | Example:
| + | |
− | | + | |
− | new Entity {
| + | |
− | name := "Person";
| + | |
− | references += new Reference {
| + | |
− | name := "someRef"
| + | |
− | type := anotherEntity
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | ==== Assignment Expressions ====
| + | |
− | They are just another syntax for invoking a setter resp. adder-operation.
| + | |
− | They return the assigned value.
| + | |
− | | + | |
− | ==== Operator Overloading ====
| + | |
− | There will be predefined operators which can be used instead of the usual function invocation syntax if there is an operator for a name and a specific number of parameters.
| + | |
− | | + | |
− | Some examples:
| + | |
− | | + | |
− | add(Object a, Object b) => a + b
| + | |
− | subtract(Object a, Object b) => a- b
| + | |
− | not(Object a) => !a
| + | |
− | | + | |
− | The && and || operators are not overwriteable because of there special semantics wrt lazy evaluation.
| + | |
− | | + | |
− | ==== create / cache semantics ====
| + | |
− | The creation expression should replace the "create extension" mechanism from Xtend 1.0.
| + | |
− | A creation of an Object is cached if the type name is suffixed with parenthesis containing any number of arguments.
| + | |
− | The arguments act as a key.
| + | |
− | | + | |
− | The scope of the caching is per execution context, which can be reused in several invocations?
| + | |
− |
| + | |
− | Examples:
| + | |
− | | + | |
− | var paramPerOperationAndName := new Parameter cachedwith op,name {
| + | |
− | this.name := name;
| + | |
− | type := aDatatype;
| + | |
− | }
| + | |
− | | + | |
− | var localSingleton := new Foo cachedwith {
| + | |
− | stuff := "bla";
| + | |
− | }
| + | |
− | | + | |
− | ==== cross referencing ====
| + | |
− | | + | |
− | We need a way to specify cross references within a declared tree. The problem is that we need a reference to a created type after it has been created and before it will be initialized. This can be accomplished by adding a special assignment construct:
| + | |
− | | + | |
− | var x := new Entity as localRef {
| + | |
− | // x is not visible here, because the right hand expression has not been evaluated so far.
| + | |
− | // localRef holds a reference to the created but not yet initialized entity.
| + | |
− | name := "Person";
| + | |
− | references += new Reference {
| + | |
− | name := "partner"
| + | |
− | type := localRef
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | === XString (Template syntax) ===
| + | |
− | We want to come up with a special datatype calle XString, which has a special literal syntax (like Xpand template syntax) and is mutable and streamable.
| + | |
− | | + | |
− | Example:
| + | |
− | myTemplate() :»
| + | |
− | package «packageName»;
| + | |
− | public class «name» {
| + | |
− | «foreach (attributes as a)»
| + | |
− | «if (a.type!=null) then»
| + | |
− | public «a.type» «a.name»;
| + | |
− | «endif»
| + | |
− | «endforeach»
| + | |
− | }
| + | |
− | «;
| + | |
− | | + | |
− | It's just a string literal with the xpand syntax within. Instead of '«' and '»' one can also use the common literal syntax 'foo' and "bar".
| + | |
− | | + | |
− | The FILE statement will be removed.
| + | |
− | Files can be opened through extensions:
| + | |
− | | + | |
− | generateCode(Entity e) :
| + | |
− | writeToFile(e.JavaFile(),e.toJava());
| + | |
− | | + | |
− | ==== Container (NOT CLEAR HOW THIS COULD WORK) ====
| + | |
− | Because XStrings are mutable and are converted to a string late, it is possible to create a tree structure containing XStrings, where
| + | |
− | you can add XStrings (or normal Strings) later on.
| + | |
− | | + | |
− | Example:
| + | |
− | toJava(Entity this) :"""
| + | |
− | package «packageName()»;
| + | |
− | «imports()»
| + | |
− |
| + | |
− | ...
| + | |
− | """;
| + | |
− | | + | |
− | cached imports(Entity this) :"""
| + | |
− | import java.util.*;
| + | |
− | """;
| + | |
− |
| + | |
− | ... to be continued (and cleaned up;-))
| + | |
− | | + | |
− | === if expression ===
| + | |
− | As seen in the previous example, we want an if-expression. using if, else keywords.
| + | |
− | | + | |
− | if (predicate) expression (else if (predicate) expression)* (else expression)? endif
| + | |
− | | + | |
− | The else part is optional and will return null if not defined.
| + | |
− | | + | |
− | Example:
| + | |
− | // The following expression will return null:
| + | |
− | if (false) "Holla" endif
| + | |
− | | + | |
− | 'endif' is optional.
| + | |
− | | + | |
− | ===No Checks anymore===
| + | |
− | Checks used to provide a declarative way of specifying constraints on types.
| + | |
− | We will come up with a framework to do the same thing, so a constraint can be written like so:
| + | |
− | | + | |
− | import assertion::Extensions;
| + | |
− | | + | |
− | check(Entity this) abstract :
| + | |
− | error("The abstract entity "+name+" has no subclasses.",
| + | |
− | subClasses.notEmpty);
| + | |
− | | + | |
− | This would be the replacement for
| + | |
− |
| + | |
− | context Entity if abstract
| + | |
− | ERROR "The abstract entity "+name+" has no subclasses." :
| + | |
− | subClasses.notEmpty;
| + | |
− | | + | |
− | No big difference I think, but we don't need to introduce a whole new concept (checks).
| + | |
− | | + | |
− | In addition it is now possible to write hierarchicly structured constraints which depend on each other:
| + | |
− | check(Property this) {
| + | |
− | dev column := findTable(entity()).columnForName(columnName());
| + | |
− | if (error("No corresponding column found ",
| + | |
− | column!=null) {
| + | |
− | error("The type does not match",
| + | |
− | column.type == this.type.sqlType())
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | Moreover one can have a name for a constraint like this (only the 'check' prefix is needed)
| + | |
− | | + | |
− | checkTableExists(Entity this) :
| + | |
− | error("No corresponding table exists ",
| + | |
− | findTable(this)!=null);
| + | |
− | | + | |
− | looks and is similar to what xunit does. And it's a good idea to have something like XUnit for our language as well.
| + | |
− | | + | |
− | === Definition of Types (later) ===
| + | |
− | So far we couldn't define Types within Xtend but had to define them using other techniques (ecore, Java, UML-profile, etc.).
| + | |
− | Defining tyoes within Xtend would be a great feature. Because it is much simpler and faster to write them in text.
| + | |
− | In addition we could define Type with logic (operations).
| + | |
− | A syntax could look like this:
| + | |
− | | + | |
− | type Entity extends Named {
| + | |
− | // simple attributes
| + | |
− | String name;
| + | |
− | Boolean isAbstract {
| + | |
− | private set(aValue)
| + | |
− | get : name.startsWith("Abstract");
| + | |
− | };
| + | |
− |
| + | |
− | // references
| + | |
− | Set<Entity> superTypes;
| + | |
− | Set<Features>* features; // asterisk means containment
| + | |
− | Set<Reference>* references subsets features;
| + | |
− |
| + | |
− | // operations
| + | |
− | doStuff(String x) : x+name;
| + | |
− | doMoreStuff(String x) {
| + | |
− | name := x;
| + | |
− | features += var f := new Feature{
| + | |
− | f.name:= x;
| + | |
− | ...
| + | |
− | };
| + | |
− | }
| + | |
− | }
| + | |
− | | + | |
− | MV: I agree that this is not the most urgent feature.
| + | |
− | | + | |
− | === BK added --- To be discussed ===
| + | |
− | ==== Add private keyword to XPand ====
| + | |
− | This is useful for marking Definitions in Xpand-Files as Private-API, especially useful when using AOP-features. Otherwise the whole generator is public API which is not intended in most cases
| + | |
− | | + | |
− | SE: We don't have "Xpand definitions" anymore. We have functions with XString expressions (which look the same).
| + | |
− | As functions have a private keyword, I think your requirement is met.
| + | |
− | | + | |
− | ==== AOP for Checks ====
| + | |
− | When modifying an expression using AOP, there might be the situation that a check is no longer valid. It has to be modified as well
| + | |
− | | + | |
− | SE: We can't modify expressions using AOP. We just can hang an Advice around a function call.
| + | |
− | I don't understand what you mean by, checks have to be modified.
| + | |
− | | + | |
− | == XPand ==
| + | |
− | === PF added --- to be discussed ===
| + | |
− | ==== Add a FOLDER keyword ====
| + | |
− | In order to create empty directory structures, it would be nice to have a FOLDER keyword.
| + | |
− | Syntax:
| + | |
− | «FOLDER expression»
| + | |