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 "RoadmapOAW5"

(Definition of Types (later))
Line 32: Line 32:
  
 
==== Reexport ====
 
==== Reexport ====
The reexport keyword will be supported.
+
The reexport keyword will be supported, so that
 +
imported stuff (types and functions) will be reexported.
 +
 
 +
Example:
 +
 
 +
myext1.ext
 +
 
 +
  foo() : 'foo';
 +
 
 +
myext2.ext
 +
 
 +
  import myext1 reexport;
 +
  bar() : 'bar';
 +
 
 +
client.ext
 +
 
 +
  import myext2;
 +
  fooBar() : foo()+bar();
  
MV: What will it do?
 
<br>BK: Reexport an import to "downstream" extensions
 
  
 
=== Generics ===
 
=== Generics ===
Line 57: Line 72:
 
   {
 
   {
 
     String myText := "test";
 
     String myText := "test";
 
 
     (Attribute)=>Boolean myClosure := e|e.name==myText;    // e is inferred from the declared type of the assignee
 
     (Attribute)=>Boolean myClosure := e|e.name==myText;    // e is inferred from the declared type of the assignee
 
 
     myList.select(myClosure);
 
     myList.select(myClosure);
 
   }
 
   }
Line 71: Line 84:
  
  
==== Declaring Closure types ====
+
==== Type signatures of functions ====
The syntax for of a closure type is
+
The syntax for of a function's type signature looks as follows:
 
+
MV: actually what you do here is not declare closure types, but rather function
+
signatures. Closures are more or less function literals. So there are several things:
+
* function signatures
+
* anonymous functions (closures)
+
* closures assigned to a variable (which makes them named functions defined via a closure)
+
* and normal functions
+
stuff like select(...) does not expect a closure, rather it is typed with a function
+
literal (something like (Attribute)=>boolean). You can pass either
+
* a literal closure
+
* a refernce to a named closure
+
* or a reference to a function
+
  
 
   (parameterTypes)=>returnType
 
   (parameterTypes)=>returnType
Line 100: Line 101:
 
   }
 
   }
  
=== Extensions/Functions ===
+
=== Functions ===
In the first version there will only be functions (aka. extensions).
+
 
Functions can be invoked either using the functional syntax or using the member syntax (operation like):
+
Functions can be invoked either using the functional syntax or using the member syntax (operation like, aka extensions):
  
 
   myFunction(foo, bar) == foo.myFuntion(bar)
 
   myFunction(foo, bar) == foo.myFuntion(bar)
Line 114: Line 115:
 
       name+"Stuff";
 
       name+"Stuff";
  
MV: What happens if the guard is false? Not executed? Another one of the same name is executed?
+
The detailed semantics of how the polymorphic resolution works (what role guards play here) and is described in the upcoming section.
<br>> BK: See next section. I'd prefer to have one expression with NO guard which is used as default if all the others evaluate to false. I think everything else will be the hell for the tooling!
+
  
 
or  
 
or  
 
 
   (private|cached) functionName(declaredParameterList) guardExpression blockExpression
 
   (private|cached) functionName(declaredParameterList) guardExpression blockExpression
 
MV: What is a block expression?
 
<br>> BK: just an expression
 
  
 
Example:
 
Example:
Line 131: Line 127:
 
   }
 
   }
  
==== Polymorphic Guards ====
+
Block expressions are explained in their own section.
The guards are used for polymorhpic resolution of a called extension.+
+
  
MV: Polymorphic is maybe the wrong term, because it is associated with subtyping.  
+
==== 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:
 
Example:
 +
given the following two functions
  
context Entity {
+
  foo(String x) : "string";
    String javaName() isAbstract : "Abstract"+name;
+
  foo(Object o) : "object";
    String javaName() : name;
+
 
  }
+
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:
  
The dynamic semantics are:  
+
  foo(String x) x.length>5 : "long";
 +
  foo(String x) : "short";
 +
 +
this assertions can be made:
  
* First select all possible features (functions and operations), based on the name and the types.
+
  foo('honolulu') == 'long'
* Then order those features by parameter types (best match first).
+
  foo("bar") == 'short'
* For each feature
+
* evaluate the guard
+
* if it doesn't evaluate to true skip it
+
* if it evaluates to true
+
* make sure that there is no second feature with the same signature where the guard evaluates to true
+
* if there is no such feature, we've found our feature -> success!
+
* else throw new Exception("Unambigous feature ... more than one feature ... bla")
+
  
* if there are features, but the guards evaluate to false, return null:
+
The semantics are as follows:  
  
MV: Why null? Why not fail?
+
* First select all possible features (functions and operations), based on the name and the given parameter types.
<br>> BK: see my comment above
+
* 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.
  
* if there is no feature, do "feature not found" (i.e. raising exception or invoking 'featureNotFound' operation)
+
pseudo code
  
The static semantics are straight forward: The guard must be of type boolean.
+
  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
  
MV: do we want to do static verification that all cases are covered?
+
* if there are features, but the guards evaluate to false, return null:
<br>> BK: see my comment above
+
 
 +
The static semantics are straight forward:  
 +
* The guard must be of type boolean.
  
 
====Extensions overwrite semantics====
 
====Extensions overwrite semantics====
Extensions with the same signature will overwrite Operations.
+
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.
 
Consider overwriting the toString() Operation  (which is invoked on String concatenations) for arbitrary meta types.
 
This will allow very readable templates.
 
This will allow very readable templates.
 +
 +
==== Sticky Extensions ====
 +
Extensions are accessible with a static scope. Unfortunately once an object leaves that scope a function (overwriting an operation) is no longer available,
 +
so the operation implementation would be used.
 +
We need a way to "attach" functions to objects, so that we can effectively "wrap" objects and add new functionality to them.
 +
 +
We have two different ideas of how this could be done:
 +
1) adding a stickedTo keyword to the first parameter of an extension:
 +
 +
toString(stickedto Entity this) : "foobar";
 +
 +
for each Entity which has been created within a scope where such a sticky extension is declared, the to String() extension will be used for this object.
 +
If an object has not been created in such a scope, the sticky function will only be used while the object is in that scope.
 +
 +
 +
2) adding an extension explicitely to an object:
 +
 +
TO BE CONTINUED
 +
 +
  
 
=== Code blocks ===
 
=== Code blocks ===
Line 182: Line 216:
 
It's something like a pseudo imperative syntax (but still is an expression!).
 
It's something like a pseudo imperative syntax (but still is an expression!).
  
MV: Variables are assign-once, right?
+
Variables are assign-once!
  
 
Example:
 
Example:
Line 226: Line 260:
  
 
==== Assignment Expressions ====
 
==== Assignment Expressions ====
They are just another syntax for invoking a setter resp. adder-operation (which will be removed).
+
They are just another syntax for invoking a setter resp. adder-operation.
 +
They return the assigned value.
  
MV: Great!
+
==== Operator Overloading ====
<br>> BK: I'd prefer not to remove them. (Compatibility) Do we intent to support operator overloading? Then, this could just be a function with two arguments
+
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:
 +
||definition|can be invoked like...
 +
|add(Object a, Object b)| a + b |
 +
|subtract(Object a, Object b)| a- b|
 +
|not(Object a) | !a|
 +
 
 +
...
  
They return the assigned value.
 
  
 
==== create / cache semantics ====
 
==== create / cache semantics ====
Line 238: Line 280:
 
The arguments act as a key.
 
The arguments act as a key.
  
MV: What is the scope of the caching? One Xtend Component invocation?
+
The scope of the caching is per execution context, which can be reused in several invocations?
<br>> BK: I don't think so. It is the creation plus the block for assignments. Is removing the create keyword a good idea? I like it the way it is. E.g. by default these kind of extensions always return the created object. in the new syntax, you'd have to do that manually or put everything in the init-block. I can see the benefits. Nevertheless I think the old one is useful as well. BTW: new Foo will still be valid, right? Without caching!
+
 
+
 
Examples:
 
Examples:
  
   var paramPerOperationAndName := new Parameter(op,name) {
+
   var paramPerOperationAndName := new Parameter !op,name! {
 
       this.name := name;
 
       this.name := name;
 
       type := aDatatype;
 
       type := aDatatype;
 
   }  
 
   }  
  
   var localSingleton := new Foo() {
+
   var localSingleton := new Foo!! {
 
       stuff := "bla";
 
       stuff := "bla";
 
   }
 
   }
 
MV: The syntax with the arguments in the params looks quite a bit like
 
a constructor. This is confusing. Can we use something else? like using
 
< and >, for example?
 
 
BK: what happens to the parameters? Nothing, right?
 
  
 
==== cross referencing ====
 
==== cross referencing ====
Line 262: Line 297:
 
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:
 
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:
  
BK: Why do we need that? From a user's point of view, this is useless, right?
+
   var x := new Entity as localRef {
 
+
 
+
   var x := new localRef:=Entity {
+
 
       // x is not visible here, because the right hand expression has not been evaluated so far.
 
       // 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.
 
       // localRef holds a reference to the created but not yet initialized entity.
Line 274: Line 306:
 
         }
 
         }
 
       }
 
       }
 
MV: hm, the syntax with the two := is not very nice. How about:
 
 
  var x := new Entity as localref {
 
  
 
=== XString (Template syntax) ===
 
=== XString (Template syntax) ===
Line 299: Line 327:
 
   generateCode(Entity e) :
 
   generateCode(Entity e) :
 
     writeToFile(e.JavaFile(),e.toJava());
 
     writeToFile(e.JavaFile(),e.toJava());
 
MV: Nice!!
 
  
 
==== Container (NOT CLEAR HOW THIS COULD WORK) ====
 
==== Container (NOT CLEAR HOW THIS COULD WORK) ====

Revision as of 07:04, 14 November 2007

Collection of features (rough)

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

  import myext1 reexport;
  bar() : 'bar';

client.ext

  import myext2;
  fooBar() : foo()+bar();


Generics

We need full-fledged generics, which can conceptually be copied from Java's generics. Maybe we can leave out some of the more advanced capabilities?

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:

  // type of e is inferred from the declaration of the 'select()' function
  myList.select(e|e.name == "test")

or

 {
   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 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) 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;
     return 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.

Sticky Extensions

Extensions are accessible with a static scope. Unfortunately once an object leaves that scope a function (overwriting an operation) is no longer available, so the operation implementation would be used. We need a way to "attach" functions to objects, so that we can effectively "wrap" objects and add new functionality to them.

We have two different ideas of how this could be done: 1) adding a stickedTo keyword to the first parameter of an extension:

toString(stickedto Entity this) : "foobar";

for each Entity which has been created within a scope where such a sticky extension is declared, the to String() extension will be used for this object. If an object has not been created in such a scope, the sticky function will only be used while the object is in that scope.


2) adding an extension explicitely to an object:

TO BE CONTINUED


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)
  • support early exit using the 'return' keyword, which forces outer code blocks to exit themselfes.

It's something like a pseudo imperative syntax (but still is an expression!).

Variables are assign-once!

Example:

 myExtension(String stuff) {
    var x := stuff.length();
    if (x>56)
       return "Foo";
    else {
       return "Bar";
    }
 }

A code block is itself an expression consisting of a list of expressions. It returns the value returned by the first executed "return-expression", or the value of the last expression.

It is possible to overwrite the scope. Example:

doStuff() {

  var x := "Foo";
  {
     var x:= "Bar";
     return 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:

definition|can be invoked like... a + b | a- b|  !a|

...


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 !op,name! {
     this.name := name;
     type := aDatatype;
  } 
  var localSingleton := new Foo!! {
     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:

  toJava(Entity this) :"""
    package «packageName()»;
      
     public class «name» {
       «FOREACH attributes AS a» 
        public «a.type» «a.name»;
       «ENDFOREACH»
     }""";

It's just a string literal with the xpand syntax within. The terminals '«' and '»' should be configurable (or there should be an alternative at least).

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)?

The else part is optional and will return null if not defined.

Example:

  // The following expression will return null:
  if (false) "Holla"

Declarative constraints

The language check will be integrated into Xtend. That is you can define checks and extensions in the same file:

allEntities(emf::EObject obj) : eRootContainer.eAllContents.typeSelect(Entity);

context Entity warning "name "+name+" should start with an uppercase letter" : 
      name.firstToLowerCase()!=name
context Entity error "duplicate name "+name : 
      allEntities(this).select(e|e.name==name).size==1;

of course you can use everything we have defined before in checks, too. Example :

context Entity error "duplicate name "+name {
      var entities := eRootContainer.eAllContents.typeSelect(Entity);
      allEntities(this).select(e|e.name==name).size==1;
}

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

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

Back to the top