Team blog

First Ceylon Tour in Paris

The Ceylon team recently had its almost yearly face-to-face meeting in Paris. For those not familiar with how the Ceylon team works (and indeed how most of the open-source development teams work in Red Hat), the Ceylon team consists in surprisingly few individuals working remotely, and distributed world-wide. This includes not only developers such as me, paid by Red Hat to work full-time on Ceylon, but also the community of Ceylon enthusiasts who contribute time, code or discussions in their free time, without financial compensation other than the satisfaction of helping Ceylon be that much better that much sooner.

This means that yes, we do all work without seeing each other in person in most cases. Naturally, we communicate a lot in order to work together, but in our case that means via the Ceylon mailing list, or IRC channel, or via our issue tracker. We don't even do voice chats. This is pretty great for the Ceylon community, as 99% of our discussions are thus online and in the open and visible. I say 99% because there's always cases where there are some one-to-one discussions we have over private channels, especially when we want to prepare surprises :)

An interesting consequence of that is that we have met surprisingly few people of the team in person. A few of us met physically during conferences, others by being geographically close, and others yet during our previous face-to-face meeting two years ago in Barcelona, when we were very few. But overall, most of us had never seen each other in person, which is why we try to hold yearly face-to-faces.

The primary purpose of meeting together in my opinion is to have social interactions and talk about something else than work for once. It also allows us to lock people in a room and prevent them from going out before they've looked at something or given their opinion on something, which is something that can't happen so easily when distributed. An example of that is when Loïc (one of our great free-time contributors) came to present us ceylon.build and wanted our opinion, which we had somewhat neglected to give previously, due to poor planning on our part.

We did also discuss things such as language features, roadmap and priorities, but those have already been reflected openly via our mailing list and issue tracker, where the discussion can continue.

The last (pretty big) advantage of being all together at the same place, was that we could take this opportunity to hold our first Ceylon Tour conference!

The first Ceylon Tour conference in Paris

Thanks to the guys from IRILL we were able to secure a very nice room to hold our first Ceylon conference, for free, where I'm glad to say lots of people showed up. We had people from from Slovenia, Germany and Austria, not to mention all over France and yes, even Paris. We were very surprised by the fact that people came from further than Paris, but it shows that Ceylon generates lots of interest.

We held the following talks during the morning :

Ceylon introduction

Ceylon idioms

Cayla and Vert.x in Ceylon

Ceylon SDK

ceylon.build

ceylon.test

Ceylon module repositories

Ceylon/Java interop

Ceylon workshop

And in the afternoon we had a great Ceylon workshop where people got to code in Ceylon, driven and helped by Gavin, and I'm glad to say that after 4 hours of workshop, most people were still not only awake, but engaged and coding, rather than just checking their email :)

The next Ceylon Tour?

All in all, we're very happy with the event, the content, the turnout and to answer the questions we got when we announced the first Ceylon Tour in Paris, yes we will try to plan another round in other locations, such as London, Berlin and the USA. If you think we should hold a Ceylon Tour event in your country/city, let us know and we'll happily discuss it.

The signature of reduce() in Ceylon

The Iterable interface defines a method named fold() with this signature:

Result fold<Result>(Result initial)
        (Result accumulating(Result partial, Element element))

Where Element is the element type of the Iterable. This method accepts an initial value, and an accumulator function which is applied to each element of the iterable object in turn. For example:

Integer sum = (1..10).fold(0)(plus);

Sometimes, we don't need the initial value, since can simply start accumulating from the first element. Following the convention used by Scala and F#, let's call this function reduce(). Then we would like to be able to write:

Integer sum = (1..10).reduce(plus);

But what should the signature of this method be? A first stab might give us:

Element reduce(Element accumulating(Element partial, Element elem))

But this signature is a bit more restrictive than it should be. It's perfectly reasonable for the result type of reduce() to be a supertype of the element type. Scala handles this using a lower bound type constraint. Transliterating this to Ceylon, using an imaginary syntax for lower bounds, it would look like:

Result reduce<Result>(Result accumulating(Result partial, Element elem))
        given Result abstracts Element

Here the lower bound constraint ensures that the first element is assignable to the first parameter of the accumulator function.

But Ceylon doesn't have lower bound type constraints. Why? Well, because it seems that we can in practice almost always use union types to achieve the same effect. So let's try that:

Result|Element reduce<Result>(
        Result accumulating(Result|Element partial, Element element))

Now let's try to implement this signature. One possibility would be:

Result|Element reduce<Result>(
        Result accumulating(Result|Element partial, Element element)) {
    assert (!empty, is Element initial = first);
    variable Result|Element partial = initial;
    for (elem in rest) {
        partial = accumulating(partial, elem);
    }
    return partial;
}

The assertion handles the case of an empty Iterable, resulting in an AssertionException if the iterable object has no first element.

Alternatively, we might prefer to return null in the case of an empty Iterable, which suggests the following implementation:

Result|Element|Null reduce<Result>(
        Result accumulating(Result|Element partial, Element element)) {
    if (!empty, is Element initial = first) {
        variable Result|Element partial = initial;
        for (elem in rest) {
            partial = accumulating(partial, elem);
        }
        return partial;
    }
    else {
        return null;
    }
}

Going back to Scala, we notice that Scala has two versions of reduce(), which are exactly analogous to the two possibilities we've just seen. The first version throws an exception in the empty case, and the second version, reduceOption(), returns an instance of the wrapper class Option.

But in Ceylon, we can do better. In Ceylon, Iterable has a slightly mysterious-looking second type parameter, named Absent, with an upper bound given Absent satisfies Null. An Iterable<T,Null>, which we usually write {T*}, is a possibly-empty iterable. An Iterable<T,Nothing>, which we usually write {T+}, is an iterable we know to be nonempty.

Thus we arrive at the following definition of reduce():

 Result|Element|Absent reduce<Result>(
        Result accumulating(Result|Element partial, Element element)) {
    value initial = first;
    if (!empty, is Element initial) {
        variable Result|Element partial = initial;
        for (elem in rest) {
            partial = accumulating(partial, elem);
        }
        return partial;
    }
    else {
        return initial;
    }
}

Now, for a "spanned" range expression like 1..n, which is nonempty, we get a non-null return type:

Integer sum = (1..n).reduce(plus);

On the other hand, for a "segmented" range expression like 1:n, which is possibly-empty, we get an optional return type:

Integer? sum = (1:n).reduce(plus);

Best of all, it never throws an exception. This is, I humbly submit, Pretty Damn Nice.

Notice just how much work union types are doing for us here. Compared to Scala's reduce()/reduceOption(), they let us eliminate:

  • a lower bound type constraint,
  • a second, effectively overloaded, version of the method, and
  • the wrapper Option class.

I've added this definition of reduce() to Iterable, and it will be available in the next release of Ceylon.

When ceylon.test met meta-model

Ceylon has had support for unit testing since milestone four, but its functionality was pretty limited due lack of annotations and meta-model at that time.

Fortunately this is not true anymore! With version 1.0 of Ceylon we also released a completely rewritten ceylon.test module. So let’s see what’s new and how we can use it now.

Tests annotations

Tests are now declaratively marked with the test annotation and can be written as top-level functions or methods inside top-level class, in case you want to group multiple tests together.

Inside tests, assertions can be evaluated by using the language’s assert statement or with the various assert... functions, for example assertEquals, assertThatException etc.

class YodaTest() {

    test
    void shouldBeJedi() {
        assert(yoda is Jedi, 
               yoda.midichloriansCount > 1k);
    }

    ...
}

Common initialization logic, which is shared by several tests, can be placed into functions or methods and marked with the beforeTest or afterTest annotations. The test framework will invoke them automatically before or after each test in its scope. So top-level initialization functions will be invoked for each test in the same package, while initialization methods will be invoked for each test in the same class.

class DeathStarTest() {

    beforeTest
    void init() => station.chargeLasers();

    afterTest
    void dispose() => station.shutdownSystems();

    ...
}

Sometimes you want to temporarily disable a test or a group of tests. This can be done via the ignore annotation. This way the test will not be executed, but will be covered in the summary tests result. Ignore annotation can be used on test functions/methods, or on classes which contains tests, or even on packages or modules.

test
ignore("still not implemented")
void shouldBeFasterThanLight() {
}

All these features are of course supported in our Ceylon IDE. Where you can create a Ceylon Test Launch Configuration or easily select what you want to run in the Ceylon Explorer and in context menu select Run-As → Ceylon Test.

test-result-view

Test command

Our command line toolset has been enhanced by the new ceylon test command, which allows you to easily execute tests in specific modules.

The following command will execute every test in the com.acme.foo/1.0.0 module and will print a report about them to console.

$ ceylon test com.acme.foo/1.0.0

You can execute specific tests with the --test option, which takes a list of full-qualified declarations literals as values. The following examples show how to execute only the tests in a specified package, class or function.

$ ceylon test --test='package com.acme.foo.bar' com.acme.foo/1.0.0
$ ceylon test --test='class com.acme.foo.bar::Baz' com.acme.foo/1.0.0
$ ceylon test --test='function com.acme.foo.bar::baz' com.acme.foo/1.0.0

More details about this command can be found here.

Next version

In the next version, we will introduce other improvements.

There will be a test suite annotation, which allows you to combine several tests or test suites to run them together:

testSuite({`class YodaTest`,
           `class DarthVaderTest`,
           `function starOfDeathTestSuite`})
shared void starwarsTestSuite() {}

You will be able to declare custom test listeners, which will be notified during test execution:

testListeners({`class DependencyInjectionTestListener`,
               `class TransactionalTestListener`})
package com.acme;

And finally you will be able to specify custom implementation of the test executor, which is responsible for running tests:

testExecutor(`class ArquillianTestExecutor`)
package com.acme;

Please note, that these APIs are not final yet, and can change. If you want to share your thoughts about it, don't hesitate and contact us.

On three-legged elephants

I've often argued that good design—of a language, library, or framework—isn't about packing in as many features as possible into your solution, rather it's about discovering a small set of interlocking features that act to reinforce each other. That is to say, we want to maximize the power/expressiveness of our solution, while simultaneously minimizing the surface area. I am, furthermore, quite often willing to sacrifice some flexibility for elegance. It's my view that elegant solutions are easier to learn, more enjoyable to use, and easier to abstract over.

With this in mind, I would like to consider a problem that language designers have been working on for at least two decades: how to combine subtype polymorphism with parametric polymorphism (generics). This is a central problem faced by any object-oriented language with static typing. Recent languages have come progressively closer to a satisfying solution, but I would like to submit, if it doesn't sound too self-serving, that Ceylon offers the most satisfying solution so far.

Our mascot is Trompon the elephant, because an elephant has four legs and would fall over if one of his legs were missing. Ceylon's type system is exactly like this! (Yes, this is a baxplanation.)

The four legs of the type system are:

  • declaration site variance
  • ad hoc union and intersection types
  • type inference based on principal typing
  • covariant refinement and principal instantiation inheritance

If we were to take away any one of those four characteristics, all of a sudden stuff that Just Works would simply not work anymore, or, even if we could make it work, it would turn out way more complex, and involve reasoning that is difficult for the programmer to reproduce.

Consider this really simple line of code:

value animals = ArrayList { Cat(), Dog(), Person() };

The inferred type of animals is ArrayList<Cat|Dog|Person>.

  • If we were to take away declaration site covariance, then animals would not be assignable to List<Animal>.
  • If we were to take away union and intersection types, then the process for inferring the type argument would be ambiguous and much more complex. (Ceylon's type argument inference algorithm is defined in two pages of pseudocode in the language specification, which sounds like a lot, until you realize how problematic and underspecified this algorithm is in other languages, and that the actual implementation of the algorithm is not much more longer.)
  • If we were to take away type inference, or principal typing, we would need to explicitly write down some uninteresting types in this line of code.

Minus any one of these characteristics, we're left with a three-legged elephant.

Principal instantiation inheritance is a kind-of hidden feature of Ceylon that we don't talk about much, even though we use it extensively throughout the design of our container types. It lets us say, for example, that a List<Element> is a Ranged<List<Element>>, and that a Sequence<Element> is a List<Element>&Ranged<Sequence<Element>>. Principal instantiation inheritance meshes really nicely with declaration-site covariance, and with ad hoc union/intersection types. Consider the following code:

List<String>|Integer[] ranged = ... ;
value span = ranged.span(0,max); 

Here, the inferred type of span is List<String>|Integer[]. Isn't that nice? The typechecker has reasoned that the principal supertype instantiation of Ranged for List<String>|Integer[] is the type Ranged<List<String>|Integer[]>, and thereby determined the perfect return type for span().

If we were to take away any one of declaration site covariance, principal instantiation inheritance, or union types, then this reasoning would no longer be sound. The elephant would fall on his ass.

Ceylon Tour comes to Paris

A Ceylon conference in Paris in January 2014

We have so many exciting things to talk about in the Ceylon ecosystem that it's impossible for everyone to keep track of everything that is happening, but if you live in Paris or not too far from it, we can help you, because we will organise our first Ceylon Tour conference in Paris in January 2014.

Ceylon Tour Paris 2014 logo

The whole Ceylon team will be there, there will be many short talks, discussions and a workshop. The conference is free, but availability is limited so we advise you to reserve your ticket as soon as possible, but only if you're sure to come (it wouldn't be fair to others to reserve a ticket and not come).

Registration, as well as all the info about this conference is detailed on the conference page.

If you live near Paris, any excuse is good to visit Paris, especially a free Ceylon conference :)

If you don't live near Paris, hopefully this is only the start and we will visit your country soon!