Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
Difference between revisions of "RoadmapOAW5"
Line 1: | Line 1: | ||
{| border=0 | {| border=0 | ||
| width=90% | | | width=90% | | ||
− | = | + | == Successor to Xpand/Xtend (Work in progress - feedback is highly appreciated) = |
− | + | It's time to think about a possible successor to Xpand/Xtend. Although the languages have proven well compared to alternatives there are a number of things which can be better and clearer based on our two years of experience with Xpand and Xtend. | |
− | + | The main improvements we want to incorporate are: | |
− | + | ||
− | + | ||
=== Imports === | === Imports === |
Revision as of 08:39, 5 December 2007
Contents
= Successor to Xpand/Xtend (Work in progress - feedback is highly appreciated)It's time to think about a possible successor to Xpand/Xtend. Although the languages have proven well compared to alternatives there are a number of things which can be better and clearer based on our two years of experience with Xpand and Xtend. The main improvements we want to incorporate are: ImportsThe 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 importA native import refers to another extension file imports all public members (types, functions, extensions). Non-native ImportA 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 definitionAll members are included without namespace information. If you need a namespace you can explicitely define one per import. ReexportThe 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(); GenericsWe 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 ;-) ClosuresWe'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 functionsThe 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) { ... } FunctionsFunctions 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 guardsUsually 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:
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
The static semantics are straight forward:
Extensions overwrite semanticsFunctions 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 overwritingAnother 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 blocksA code block is the replacement for chain expressions ( a-> b-> x) with the additional features:
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 expressionWe 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 ExpressionsThey are just another syntax for invoking a setter resp. adder-operation. They return the assigned value. create / cache semanticsThe 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 referencingWe 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 } } Operator OverloadingThere 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. Overloading accessorsIf a function's signature follows the pattern String getFoo() it can be invoked using property syntax: this.foo If a function's signature follows the pattern void setFoo(String) it overwrites the modify of the 'foo' property, hence it will be used within assignments: foo := 'Holla' It is also possible to use such functions without having a corresponding property like: getJavaName(Entity this) { if abstract then 'Abstract'name else name endif } which can be used like: myTemplate(Entity this) :» public class «javaName» { } «; TemplatesA template is essentially a function returning a String. I always disliked that it is not possible to mix functions and templates within one file. I also find the invocation of templates (EXPAND bla FOR foo) too verbose. 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()); XString - mutable, streamable, lazy stringWe want to come up with a special datatype called XString, which is mutable, streamable and evaluated lazy (on invocation of toString()). 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()»; «(def imports:=» import java.util.*; «)» public class ... »assertImported(imports,"java.math.*")« } «; assertImported(XString this, String import) : if !this.contains(import) then this.append("import "import";"); ;
if expressionAs 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.
BK added --- To be discussedAOP for ChecksWhen 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 wave an Advice around a function call. Shouldn't the designer of a cartridge have this in mind when declaring Join Points to be adviced by clients? I think if an advice breaks an existing constraint it breaks the contract of the adviced function. PF added --- to be discussedAdd a FOLDER keywordIn order to create empty directory structures, it would be nice to have a FOLDER keyword. Syntax: «FOLDER expression» SE: I think such things should be implemented using library functions. |