Ceylon M6 progress report

So it looks like it's time again for me to offer my usual lame excuses for another late milestone release of Ceylon. Well, I suppose all that really matters is: it's coming soon!

Ceylon M6 will be the first feature-complete implementation of the language specification, incorporating the following headline changes:

  • new syntax for invoking super-interface members,
  • nonempty variadic parameters
  • try with resources,
  • the ** scaling multiplication operator,
  • "static" member references,
  • metamodel and metamodel expressions, and
  • annotations.

We have not yet decided if support for serialization will make it into M6.

Invoking super-interface members

Previously, we were using a rather ugly and arbitrary syntax, for example, List::equals(that), to invoke an overridden member of a superinterface. Now we can just write:

super.equals(that)

except in cases where this is ambiguous (the member is ambiguously inherited from more than one supertype), in which case we can use the widening of operator to eliminate the ambiguity:

(super of List<T>).equals(that)

(We now treat super as a value whose type is the intersection of all immediate supertypes of the current type.)

Nonempty variadic parameters

You may now define a variadic function that requires at least one argument, for example:

String max(String+ strings) {
    value max = strings.first;
    for (string in strings.rest) {
        if (string>max) {
            max = string;
        }
    }
    return max;
}

The type of such a function is written String(String+), meaning, of course, Callable<String,[String+]>.

try with resources

This constuct works almost exactly like in Java. For example:

try (Transaction()) { ... }

Scaling multiplication operator

A top user request was support for "scaling" multiplication for vectors, matrices, durations, etc. We've introduced the interface Scalable and the ** operation to let you write things like:

Vector scaled = 2 ** vector;

Static member references

Static member references let us obtain a reference to a member of a type without providing an instance of the type. For example:

  • Person.name refers to the attribute name declared by the class Person, and
  • Object.equals refers to the method equals declared by the class Object.

The type of a static method reference is a function type where the first parameter list accepts an instance of the type that declares the member.

  • Person.name is of type String(Person), and
  • Object.equals is of type Boolean(Object)(Object).

Static attribute references are especially useful, since we can pass them directly to map():

Person[] people = .... ;
{String*} names = people.map(Person.name);

This functionality is already working.

The metamodel

We call Ceylon a "higher-order" language partly because functions, classes, methods, and attributes are typed values. For example, the class Person is a value of type Person(Name):

Person(Name) createPerson = Person;
Person person = createPerson(Name("Gavin", "King"));

However, Ceylon takes the notion of "higher-order" significantly further. The language lets us write typesafe code that reasons about the program itself, by providing a typesafe metamodel.

A metamodel expression is enclosed in backticks:

Class<Person,[Name]> personClass = `Person`;
Attribute<Person,Name> nameAttribute = `Person.name`;
Method<Object,Boolean,[Object]> equalsMethod = `Object.equals`;

We can use a metamodel to obtain the name and annotations of a declaration, or to query a type for members by their return type, parameter types, annotations, etc.

It's taking us a bit of work to get the metamodel just right, and that's the main thing that has been holding up the M6 release.

Annotations

Annotations are discussed here. The basic concept hasn't changed much in our initial implementation, but work is ongoing.