Blog of Gavin King

Modelling failure in Ceylon

In all programming languages, we need to deal with operations than can "fail":

  • a pure function might fail to produce a result, or
  • an impure function might fail to produce its desired side-effect (create a new file, or whatever).

In neither case can we just blindly continue with the rest of the computation. In the first case, the result of the function might be the input to some other function. In the second case, subsequent operations might assume that the side-effect occurred (that the file now exists, or whatever).

Thus, it's clear that there must be some way for an operation to signal failure to the calling code. There are two broad mechanisms provided by programming languages for signalling failure:

  1. Failure may be indicated via return values: an error code, null, a union type, or a sum (enumerated) type, for example, Option/Maybe or Either.
  2. Failures may be signalled and handled within some sort of exception or "panic" facility.

Most modern programming languages support both of these mechanisms, though of course the details vary. In particular, languages offer varying degrees of typesafety.

  • In languages with proper support for sum types or union types, return values may be used to model failure in a very robust and typesafe way.
  • In languages with some sort of effect typing, for example, Java-style checked exceptions, the exceptions are themselves typesafe.

By typesafe what I mean is that an operation that can fail declares the possibility of failure in its signature, and the immediately calling code is forced by the compiler to explicitly handle the failure.

Types of failure

So what facilities should a language offer for modelling failure? Return codes or exceptions? Typesafe or not? To arrive at a partial answer to this question, let's start with the following classification of "failures":

  • Some failures represent problems that the immediately calling code is very unlikely to be able to recover from. Examples include transaction rollbacks, network failures, low memory conditions, or stack overflows.
  • Some failures are typically the result of bugs in the program logic. Examples include assertion failures, division by zero, and use of null pointers. This is a class of failure that, as far as possible, we would like to detect at compile time, but no type system will ever be powerful enough to detect all of these failures. After a few minutes of thought, you should be able to convince yourself this class of problems is actually a subclass of the first class: how can any computation possibly recover meaningfully from a bug in its own logic?
  • Finally, there are "failures" that often represent recoverable conditions. For example, one might recover from a nonexistent file by creating a file. Note that failures in this class need not always be recoverable.

Given this classification, I arrive relatively quickly at the following conclusions.

Handling recoverable failures

For "recoverable" conditions, the failure should be typesafe. The compiler should be able to verify that the calling code has made an explicit decision on what to do about the failure: recovering from it, or transforming it into an unrecoverable failure. We want to prevent recoverable failures from going unnoticed by accident.

It's clear that unchecked exceptions—or other untypesafe solutions such as returning null in a language like Java where null is untypesafe—don't prevent this, and allow failure conditions to go unnoticed, leading to bugs.

The most convenient, elegant, and efficient way to represent a recoverable failure is a union-typed return value. For example, if I have a function for parsing JSON, and it can fail for illegal input, I could use a function with the following signature:

JsonObject|ParseError parseJson() => ... ;

Or, if it seems that ParseError carries no useful information, I could just use Null instead:

JsonObject? parseJson() => ... ;

Alternatively, in some advanced cases, one could use a sum (enumerated) return type.

interface ParseResult of ParseSuccessful | ParseError {}
class ParseSuccessful(shared JsonObject result) satisfies ParseResult {}
class ParseError(shared String message) satisfies ParseResult {}

ParseResult parseJson() => ... ;

This is not usually necessary in Ceylon, however.

Handling unrecoverable failures

Now, for "unrecoverable" conditions, the failure should be an untyped (unchecked) exception. For an unrecoverable failure, we shouldn't be polluting the calling code with concerns it can't possibly do anything useful with. We want the failure to propagate quickly and transparently to some centralized, generic, infrastructure-level error handling.

Note that, since unchecked exceptions don't appear in the signature of the operation, the caller doesn't receive any kind of "fair warning" that they can occur. They represent a sort of designed-in "hole" in the type system.

When in doubt

But wait, you're probably thinking, haven't I left a huge question begging here?

What about failure that doesn't fall cleanly into "recoverable" or "unrecoverable"?

Isn't there a huge grey area there, filled with failures that are sometimes recoverable by the immediately-calling code?

Indeed there is. And I would say that, as a rule of thumb, treat these failures as recoverable.

Consider our parseJson() function above. A syntax error in the given JSON text could easily be the result of a bug in our program, but, crucially, it's not a bug in parseJson() itself. The code that knows whether it's a program bug or something else is the calling code, not the parseJson() function.

And it's always easy for the calling code to transform a recoverable failure into an unrecoverable failure. For example:

assert (is JsonObject result = parseJson(json));
//result must be a JsonObject here

Or:

value result = parseJson(json);
if (is ParseError result) {
    throw AssertionError(result.message);
}
//result must be a JsonObject here

That is, when in doubt, we make the calling code explicitly document its assumptions.

Looking at this another way, we err on the side of typesafety, since having too many unchecked exceptions starts to undermine the whole value of the static type system.

Furthermore, it's quite likely that the calling code is better placed to produce an error with more meaningful information than the code it's calling (though I have not shown that in the snippets above).

Three things to consider

I promised a "partial" answer to my original question, because there are still a couple of questions that I'm not sure I have a completely bottled answer to, and there's debate over these issues in the Ceylon community.

Is AssertionError overused?

First, what kind of failures are legitimate uses of AssertionError? Should every AssertionError represent a bug in the program? Is it ever reasonable for a library to throw an AssertionError when it encounters a situation it considers misuse of its API? Is it acceptable for generic exception handling code to recover from an AssertionError, or should AssertionErrors be considered fatal?

My answers would be yes, yes, and yes. But perhaps that implies that it was a mistake to follow Java in making AssertionError an Error instead of a plain Exception. (This leads to a larger debate about the role of Error.)

Is Null overused?

Second, the class Null is a seductively convenient way to represent failure of functions which return a value. But are we overusing it? Would it have been better to make the return type of Map<Key,Item>.get() be Item|NoItem<Key> instead of the much more generic type Item?, that is, Item|Null?

Perhaps. In a sense, returning null is like throwing Exception: a little too generic. But since a null return value must be handled by the immediately calling code, which is much better placed to know what this instance of null represents, it's not as harmful as a generic Exception which is handled far from its source.

Whether you agree with that or not, it still might be best to avoid operations where null can result from multiple different failure conditions. I have broken this rule in the past, and I'm going to be more careful in future.

Functions with no useful return value

Third, for functions with no useful return value, that is, functions which are called only for their side-effect—where the calling code has the option of simply ignoring any return value representing failure—should we err on the side of throwing an exception?

Or, alternatively, should the language offer some way to force the caller to do something with the return value of non-void function?

Ceylon doesn't have (and won't have) checked exceptions, but one could argue that this is the one situation where they would be most useful.

Conclusion

So, ultimately, there are some unanswered questions, and grey areas, but it seems to me that at least we have a rather strong conceptual framework in which to investigate these problems. And it's clear that the combination of facilities—union types, together with unchecked exceptions—is a powerful foundation for robust failure handling.

Dependency injection in Ceylon with Weld and Guice

I'm personally ambivalent about the benefits of dependency injection. On the one hand, I recognize its usefulness in certain container environments such as Java EE. (For the record, I was the author of the CDI 1.0 specification, with my JCP Expert Group.) On the other hand, given the nature of what I've been working on for the last few years, I don't really have a use for it in my own programs.

But there are plenty of folks out there who swear by dependency injection, and ask me what Ceylon offers in this area. The short answer is: nothing special; the Ceylon SDK is architected around the notion of modular libraries. It offers neither framework nor container. This makes the SDK as general purpose as possible, meaning it can be reused from any other container environment (say, Java EE, vert.x, OSGi, or whatever).

So if you want dependency injection in Ceylon today, you're going to have to use a container written in Java. Fortunately, Ceylon 1.2 features such excellent interoperation with Java that this results in barely any friction at all. Surely someone will write a dependency injection container in Ceylon some day, but, as we're about to see, there's no urgency at all.

I'm going to explore:

  • Weld, which is the reference implementation of CDI, developed by my colleagues at Red Hat, and,
  • in the interests of giving equal time to a "competitor", Google's Guice, originally written by my friend Bob Lee, which was one of the major influences on the CDI specification.

These are my favorite containers for Java, though of course Spring has legions of fans. Perhaps I'll find time to play with it some other day.

You can find the example code in the following Git repository:

https://github.com/ceylon/ceylon-examples-di

Weld

I found it extremely straightforward to use Weld in Ceylon, except for one relatively minor problem, which I'll mention below.

Module descriptor for Weld

Weld provides a fat jar in Maven Central, which makes it especially easy to use in Ceylon. I used the following module descriptor to download Weld from Maven Central and import it into my project:

native("jvm")
module weldelicious "1.0.0" {
    import "org.jboss.weld.se:weld-se" "2.3.1.Final";
    import ceylon.interop.java "1.2.0";
}

Where org.jboss.weld.se is the Maven group id, and weld-se is the Maven artifact id. (I have not the slightest clue what these things actually mean, I just know there are two of them.)

I also imported the Ceylon SDK module ceylon.interop.java because I'm going to use its javaClass() function.

Bootstrapping Weld

Though it's not part of the CDI specification, Weld offers a very simple API for creating a container. I copy/pasted the following code from stackoverflow:

import org.jboss.weld.environment.se { Weld }

shared void run() {

    value container = Weld().initialize();

    //do stuff with beans
    ...

    container.shutdown();

}

I tried to run this function.

Gotcha!

Just like every other CDI developer ever, I forgot the beans.xml file. Fortunately, Weld gave me a rather clear error message. Not quite as poetic as "se te escapó la tortuga", perhaps, but good enough to remind me of this requirement of the spec. (Yeah, the spec I wrote.)

To resolve the problem, I added an empty file named beans.xml to the directory resource/weldelicious/ROOT/META-INF, which is the magical location to use if you want Ceylon to put a file into the META-INF directory of a module archive.

Defining Weld beans

I defined the following interface, for a bean I hoped to inject:

interface Receiver {
    shared formal void accept(String message);
}

Next, I defined a bean which depends on an instance of this interface:

import javax.inject { inject }

inject class Sender(Receiver receiver) {
    shared void send() => receiver.accept("Hello!");
}

(The inject annotation is the thing you write @Inject in Java.)

Finally, we need a bean which implements Receiver:

class PrintingReceiver() satisfies Receiver {
    accept = print;
}

Obtaining and calling a bean

Going back to the run() function, I added some code to obtain a Sender from the container, and call send():

import org.jboss.weld.environment.se { Weld }
import ceylon.interop.java { type = javaClass }

shared void run() {

    value container = Weld().initialize();

    value sender 
            = container
                .select(type<Sender>())
                .get();

    sender.send();

    weld.shutdown();

}

Note that I'm using the javaClass() function to obtain an instance of java.lang.Class for the Ceylon type Sender. An alternative approach, which uses only a CDI API, and which also works for generic types, is to use javax.enterprise.inject.TypeLiteral:

value sender 
        = container
            .select(object extends TypeLiteral<Sender>(){})
            .get();

Unfortunately, that's a little more verbose.

Named constructor injection

Using a little quick fix in the IDE, we can transform the Sender class into a class with a default constructor:

class Sender {
    Receiver receiver;
    inject shared new (Receiver receiver) {
        this.receiver = receiver;
    }
    shared void send() => receiver.accept("Hello!");
}

As far as Weld is concerned, this is the same as what we had before.

But we can even give our constructor a name:

class Sender {
    Receiver receiver;
    inject shared new inject(Receiver receiver) {
        this.receiver = receiver;
    }
    shared void send() => receiver.accept("Hello!");
}

Due to unanticipated serendipity, this actually Just Works.

Method and field injection

I don't think that method or field injection is a very natural thing to do in Ceylon, and so I don't recommend it. However, it does work, just as long as you mark any fields initialized by injection with the late annotation:

This works, but doesn't feel very Ceylonic:

class Sender() {
    inject late Receiver receiver;
    shared void send() => receiver.accept("Hello!");
}

This works too:

class Sender() {
    late Receiver receiver;
    inject void init(Receiver receiver) {
        this.receiver = receiver;
    }
    shared void send() => receiver.accept("Hello!");
}

Using a CDI producer

One nice thing about using Ceylon with Weld is that you can use the produces annotation on a toplevel function.

import javax.enterprise.inject { produces }

produces Receiver createReceiver() 
        => object satisfies Receiver {
            accept = print;
        };

CDI qualifiers

We can define CDI qualifier annotations in Ceylon:

import javax.inject { qualifier }

annotation Fancy fancy() => Fancy();
final qualifier annotation class Fancy() 
        satisfies OptionalAnnotation<Fancy> {}

A qualifier annotation must be applied at both the injection point and to the bean or producer function. First, I annotated the bean class:

fancy class FancyReceiver() satisfies Receiver {
    accept(String message) 
            => print(message + " \{BALLOON}\{PARTY POPPER}");
}

Next, I tried annotating an injected initializer parameter:

//this doesn't work!
inject class Sender(fancy Receiver receiver) {
    shared void send() => receiver.accept("Hello!");
}

Unfortunately, this didn't work. When compiled to Java bytecode, Ceylon actually places this fancy annotation on a generated getter method of Sender, not on the parameter, and Weld only looks for qualifier annotations on injected parameters. I had to use constructor injection to make the qualifier work right:

//this does work
class Sender {
    Receiver receiver;
    inject shared new (fancy Receiver receiver) {
        this.receiver = receiver;
    }
    shared void send() => receiver.accept("Hello!");
}

For the record, qualifier annotations also work with method injection. They don't work with field injection.

This was the only disappointment I had using Weld with Ceylon, and I believe I already know how to solve this in Ceylon 1.2.1.

Scoped beans

You can define scoped beans (beans with what the CDI spec calls a normal scope) in Ceylon, just by applying a scope annotation to the bean:

import javax.enterprise.context { applicationScoped }

applicationScoped
class PrintingReceiver() satisfies Receiver {
    accept = print;
}

However, there's something to be careful of here: CDI creates proxies for scoped beans, and since the operations of a Ceylon class are "final" by default, you have a choice between:

  • annotating all operations of the bean default, or
  • injecting an interface instead of the concrete bean class.

I think the second option is a much better path to go down, and is probably even the best approach in Java.

Surely the same caveat applies to beans with CDI interceptors or decorators, though I did not test that.

Weld offers lots of additional functionality which I did not have time to test, but that I anticipate will work in Ceylon.

Guice

Guice was also pretty easy to get set up, though I wasted a bit of time on the Maven side of things.

Module overrides for Guice

Guice doesn't come in a fat jar, so we'll have to deal with a common problem when using Maven modules from Ceylon. Maven is designed for a flat Java classpath, so a Maven module doesn't come with metadata about which of its dependencies are re-exported via its public API. There are three basic strategies for solving this problem:

  1. Compile and run with a flat classpath by using --flat-classpath. This makes Ceylon work like Java, and robs us of module isolation.
  2. Use --export-maven-dependencies to re-export all dependencies of every Maven module.
  3. Use an overrides.xml file to explicitly specify which dependencies are re-exported.

We're going to go with option 3, since it's the hardest.

But wait—you must be thinking—XML?! And yeah, don't worry, we hate XML just as much as you do. This is a stopgap measure until Ceylon has real assemblies. Once we have assemblies, you'll be able to override module dependencies in a Ceylon assembly descriptor.

Anyway, after that longwinded preamble, all I had to do was mark javax.inject as a shared dependency:

<overrides xmlns="http://www.ceylon-lang.org/xsd/overrides">
    <module groupId="com.google.inject" 
         artifactId="guice" 
            version="4.0">
        <share groupId="javax.inject" 
            artifactId="javax.inject"/>
    </module>
</overrides>

You're very welcome to copy and paste the above bit of boilerplate into your own Ceylon and Guice projects.

Module descriptor for Guice

The following module descriptor fetches Guice and its dependencies from Maven Central, and imports Guice into the project:

native("jvm")
module guicy "1.0.0" {
    import "com.google.inject:guice" "4.0";
    import ceylon.interop.java "1.2.0";
}

Code we can reuse from the Weld example

Since Guice recognizes the inject annotation defined in javax.inject, we can reuse the definitions of Sender, Receiver, and PrintingReceiver we started out with above.

import javax.inject { inject }

inject class Sender(Receiver receiver) {
    shared void send() => receiver.accept("Hello!");
}

interface Receiver {
    shared formal void accept(String message);
}

class PrintingReceiver() satisfies Receiver {
    accept = print;
}

Bootstrapping Guice

Guice has the notion of a module object, which has a collection of bindings of types to objects. Unlike Weld, which automatically scans our module archive looking for beans, bindings must be registered explicitly in Guice.

import ceylon.interop.java {
    type = javaClass
}
import com.google.inject {
    AbstractModule,
    Guice {
        createInjector
    },
    Injector
}

Injector injector
        = createInjector(
    object extends AbstractModule() {
        shared actual void configure() {
            bind(type<Receiver>()).to(type<PrintingReceiver>());
        }
    });

This code binds the implementation PrintingReceiver to the interface Receiver.

Obtaining and calling an object

Now it's easy to obtain and call a container-bound instance of Sender:

import ceylon.interop.java {
    type = javaClass
}

shared void run() {
    value sender = injector.getInstance(type<Sender>());
    sender.send();
}

We're again using javaClass(), but Guice has its own TypeLiteral. (For the record, CDI stole TypeLiteral from Guice.)

import com.google.inject {
    Key,
    TypeLiteral
}

shared void run() {
    value key = Key.get(object extends TypeLiteral<Sender>(){});
    value sender = injector.getInstance(key);
    sender.send();
} 

Constructor injection

Injection into default constructors works, and looks exactly like what it looks like for Weld. However, injection into named constructors doesn't work with Ceylon 1.2.0 and Guice 4.0. This is pretty easy to fix on our side, and so it should work in Ceylon 1.2.1.

Method and field injection

The creators of Guice strongly prefer constructor injection, which is, as we have observed, also more natural in Ceylon. But method and field injection works fine, as with Weld, if you mark injected field late.

Provider methods

Guice scans the module object for methods annotated provides.

import com.google.inject {
    AbstractModule,
    Guice {
        createInjector
    },
    Injector,
    provides
}

Injector injector
        = createInjector(
    object extends AbstractModule() {
        shared actual void configure() {}
        provides Receiver createReceiver()
                => object satisfies Receiver {
                    accept = print;
                };
    });

I find this significantly inferior to the approach in CDI where producer methods can be defined as toplevel functions.

Binding annotations

Guice's binding annotations work almost exactly like CDI qualifier annotations (since that's where CDI copied them from). The code to define a binding annotation is exactly the same as for Weld.

import javax.inject { qualifier }

annotation Fancy fancy() => Fancy();
final binding annotation class Fancy() 
        satisfies OptionalAnnotation<Fancy> {}

The qualifier annotation must be specified when defining a binding:

Injector injector
        = createInjector(
    object extends AbstractModule() {
        shared actual void configure() {
            bind(type<Receiver>())
                .to(type<PrintingReceiver>());
            bind(type<Receiver>())
                .annotatedWith(Fancy()) //binding annotation
                .to(type<FancyReceiver>());
        }
    });

Just like in Weld, qualifier annotations work with constructor or method injection, but don't currently work with initializer parameter or field injection.

Scoped beans

Like CDI, Guice has scoped objects.

import com.google.inject { singleton }

singleton
class PrintingReceiver() satisfies Receiver {
    accept = print;
}

I didn't have time to test this feature of Guice extensively, but I happen to know that Guice doesn't use proxies, so it's not necessary to use an interface instead of a concrete class.

Conclusion

If you want dependency injection in Ceylon, it's clear that you have at least two excellent options.

Porting Ceylon IDE to IntelliJ

We've had many questions about developing Ceylon in IntelliJ IDEA, so I thought it would be worth a quick status update.

TL;DR: The screenshots are below.

As you might know, Ceylon already has the most feature rich IDE of any modern language for the JVM, with some features that even the Java IDE for Eclipse doesn't have. But IntelliJ users don't like having to switch to Eclipse when they code Ceylon, so a few months ago we got serious about porting Ceylon IDE to IntelliJ. Bastien Jansen is working on this fulltime, together with David Festal from SERLI.

The approach they're taking is to refactor reusable functionality of Ceylon IDE out into a separate project ceylon-ide-common. Simultaneously they're rewriting the common code in Ceylon (which David reports is really helping simplify and improve the code). Then this "abstracted" code is reused in the ceylon-ide-intellij project—which is also being written in Ceylon—and in ceylon-ide-eclipse. Thus, ceylon-ide-common gives us a common foundation for both IDEs, and enables us to get some really sophisticated functionality into the IntelliJ IDE very quickly.

Even better, once ceylon-ide-common is stabilized, we can reuse it elsewhere, for example, in the Web IDE, or in the new (experimental) plugin for NetBeans. Bastien was able to add autocompletion to the experimental Netbeans plugin in about 2-3 hours.

This also all demonstrates just how well Ceylon's Java interop works in practice. Here we have Java calling Ceylon and Ceylon calling back to Java all over the place!

The IntelliJ plugin isn't really usable just yet, since David is still working on abstraction of the Ceylon IDE incremental builder, but we expect to have a first release in a handful of months.

Screenshots

Ceylon IDE for IntelliJ already features completion:

completion

Including linked mode argument completion:

linked mode

Outline view and hover:

outline     hover

Live error reporting:

errors

And execution:

run

Much more functionality is coming soon!

Ceylon 1.2.0 is now available

After a full year in development, and with more than 1500 issues closed, Ceylon 1.2.0 brings new language features, including:

  • named constructors,
  • serialization,
  • native declarations,
  • improved flow-sensitive typing,
  • destructuring for tuples and entries
  • let, switch, if, and object expressions, and
  • more powerful annotation constraints.

Furthermore, the typechecker and JavaScript backend now support type functions as an experimental feature.

Also part of this release are enhancements to the tooling, such as:

  • a new debugger for Ceylon, and
  • the Java EE packaging command, ceylon war.

As always, this release incorporates hundreds of other bugfixes and enhancements.

About Ceylon

Ceylon is a modern, modular, statically typed programming language for the Java and JavaScript virtual machines. The language features a flexible and very readable syntax, a unique and uncommonly elegant static type system, a powerful module architecture, and excellent tooling, including an awesome Eclipse-based IDE.

Ceylon enables the development of cross-platform modules that execute portably in both virtual machine environments. Alternatively, a Ceylon module may target one or the other platform, in which case it may interoperate with native code written for that platform.

In the box

This release includes:

  • a complete language specification that defines the syntax and semantics of Ceylon in language accessible to the professional developer,
  • a command line toolset including compilers for Java and JavaScript, a documentation compiler, a test runner, a WAR archive packager, and support for executing modular programs on the JVM and Node.js,
  • a powerful module architecture for code organization, dependency management, and module isolation at runtime,
  • the language module, our minimal, cross-platform foundation of the Ceylon SDK, and
  • a full-featured Eclipse-based integrated development environment.

Language

Ceylon is a highly understandable object-oriented language with static typing. The language features:

  • an emphasis upon readability and a strong bias toward omission or elimination of potentially-harmful or potentially-ambiguous constructs and toward highly disciplined use of static types,
  • an extremely powerful and uncommonly elegant type system combining subtype and parametric polymorphism with:
    • first-class union and intersection types,
    • both declaration-site and use-site variance, and
    • the use of principal types for local type inference and flow-sensitive typing,
  • a unique treatment of function and tuple types, enabling powerful abstractions, along with the most elegant approach to null of any modern language,
  • first-class constructs for defining modules and dependencies between modules,
  • a very flexible syntax including comprehensions and support for expressing tree-like structures,
  • fully-reified generic types, on both the JVM and JavaScript virtual machines, and a unique typesafe metamodel.

More information about these language features may be found in the feature list and quick introduction.

This release introduces the following new language features and improvements:

  • named constructors,
  • support for serialization libraries in the metamodel,
  • the native annotation, which allows the use of platform-dependent code in cross-platform modules
  • improvements to flow-sensitive typing,
  • destructuring for tuples and entries,
  • let, switch, and if expressions,
  • inline object expressions,
  • more powerful annotation constraints,
  • type argument inference for function references,
  • an improved algorithm for type argument inference in invocation expressions,
  • improvements to analysis of disjointness for sequence types,
  • new type abbreviations, T[N] and T(*A),
  • an abbreviated syntax for identifying the containing `package`, `module`, `class`, or `interface`,
  • inline variable defininition in switch
  • the ability to directly import members of a singleton object,
  • relaxation of type constraint checking where unnecessary to ensure soundness, and
  • experimental support for type functions (higher-order generics) and references to generic functions (higher-rank polymorphism).

Language module

For Ceylon 1.2, the following new APIs were introduced to the language module:

  • the map() and set() functions allow creation of immutable Maps and Sets with no dependency to ceylon.collection,
  • distinct, frequences(), group(), tabulate(), and summarize() were added to Iterable,
  • getOrDefault(), defaultNullItems(), and coalescedMap were added to Map,
  • Collection.permutations() was added,
  • formatFloat() was added,
  • the Contextual interface was added, a cross-platform abstraction of thread-local values,
  • some operations of List were split out onto the new SearchableList interface, and
  • arrayOfSize() was deperecated and replaced with the constructor Array.ofSize().

Furthermore, some native implementation code has been rewritten in Ceylon using native.

Compiler and command line tools

Enhancements to the Java compiler include:

  • much improved interoperation with Maven, including support for overriding module metadata with overrides.xml, and --flat-classpath and --auto-export-maven-dependencies,
  • all compiled classes are now Serializable and have default constructors, allowing much smoother interoperation with certain Java frameworks,
  • improved interoperation with Java annotations, and
  • basic support for interoperation with libraries written in Scala.

The JavaScript compiler now supports type functions, allowing the use of higher-order and higher-rank polymorphism in Ceylon. These experimental features are not yet supported by the Java compiler.

There are several new features and improvements to the command line toolset:

  • the ceylon war command repackages a module as a Java EE WAR archive,
  • the ceylon browse command opens module documentation in the browser,
  • multiple commands can be given simultaneously, for example ceylon compile,doc,run com.redhat.hello,
  • ceylon help command and ceylon --help now page output by default, and
  • the ceylon command architecture now supports writing plugins in Ceylon.

IDE

Ceylon IDE now features the following improvements, along with many bugfixes and a number of performance enhancements:

  • a brand new debugger for Ceylon,
  • extensive support for new language features including constructors and native,
  • improvements to the powerful Change Parameter List refactoring,
  • the Inline refactoring can now inline a type alias,
  • filtering of packages from searches and completions,
  • many new quick fixes and assists,
  • Paste Java as Ceylon,
  • the popup Outline can now show inherited members,
  • the redesigned Open Declaration dialog now shows documentation,
  • keyboard shortcuts were added for certain quick assists,
  • support for Eclipse's new dark theme,
  • refactored preferences pages, with much greater customizability, including
  • two new alternative syntax highlighting themes, along with an alternative icon set.

A number of important subsystems have been abstracted and rewritten in Ceylon, to support the ongoing development of the new IntelliJ-based IDE for Ceylon.

SDK

The platform modules, recompiled for 1.2.0, are available in the shared community repository, Ceylon Herd.

This release introduces two new platform modules:

  • ceylon.transaction provides support for distributed transaction processing, and
  • ceylon.regex provides regular expressions.

Along with several API enhancements and bugfixes, including:

  • ceylon.time now has functions for parsing ISO 8601 formatted dates, times, and datetimes,
  • ceylon.locale now supports formatting zoned times, and parsing dates and times,
  • ceylon.interop.java now has javaClassFromDeclaration(),
  • ceylon.net now has redirect(), and its Uri is now immutable, and
  • the collection types in ceylon.collection now offer additional named constructors.

OpenShift cartridge

The Ceylon cartridge for OpenShift has been improved and updated to support Ceylon 1.2.

Web IDE

You can try Ceylon using the redesigned Web IDE, now rewritten in Ceylon, and featuring syntax highlighting, interactive error reporting, autocompletion, online documentation, and persistence and code sharing via Gist.

The Web IDE serves a dual purpose as a standard example demonstrating the use of Ceylon for web application development and deployment to the OpenShift cloud platform.

Community

The Ceylon community site, http://ceylon-lang.org, includes documentation, and information about getting involved.

Source code

The source code for Ceylon, its specification, and its website, is freely available from GitHub.

Information about Ceylon's open source licenses is available here.

Issues

Bugs and suggestions may be reported in GitHub's issue tracker.

Migrating from Ceylon 1.1

Migration from Ceylon 1.1 is easy. To recompile a module for 1.2:

  • First ensure that its dependencies have also been recompiled.
  • If it imports a Ceylon SDK platform module, upgrade the version number specified by the module import statement from "1.1.0" to "1.2.0" .
  • If it imports any platform-native module, annotate its module declaration native("jvm") or native("js"), depending upon the target platform. This step does not apply to cross-platform modules.
  • If, when recompiling, you encounter errors on assert statements, try removing the assertion (the improvements to flow typing now make some type assertions redundant).

Acknowledgement

As always, we're deeply grateful to the community volunteers who contributed a substantial part of the current Ceylon codebase, working in their own spare time. The following people have contributed to Ceylon:

Gavin King, Stéphane Épardaud, Tako Schotanus, Tom Bentley, David Festal, Enrique Zamudio, Bastien Jansen, Emmanuel Bernard, Aleš Justin, Tomáš Hradec, James Cobb, Ross Tate, Max Rydahl Andersen, Mladen Turk, Lucas Werkmeister, Roland Tepp, Diego Coronel, Matej Lazar, John Vasileff, Toby Crawley, Julien Viet, Loic Rouchon, Stephane Gallès, Ivo Kasiuk, Corbin Uselton, Paco Soberón, Michael Musgrove, Daniel Rochetti, Henning Burdack, Luke deGruchy, Rohit Mohan, Griffin DeJohn, Casey Dahlin, Alexander Altman, Alexander Zolotko, Alex Szczuczko, Andrés G. Aragoneses, Anh Nhan Nguyen, Brice Dutheil, Carlos Augusto Mar, Charles Gould, Chris Gregory, klinger, Martin Voelkle, Mr. Arkansas, Paŭlo Ebermann, Vorlent, Akber Choudhry, Renato Athaydes, Flavio Oliveri, Michael Brackx, Brent Douglas, Lukas Eder, Markus Rydh, Julien Ponge, Pete Muir, Nicolas Leroux, Brett Cannon, Geoffrey De Smet, Guillaume Lours, Gunnar Morling, Jeff Parsons, Jesse Sightler, Oleg Kulikov, Raimund Klein, Sergej Koščejev, Chris Marshall, Simon Thum, Maia Kozheva, Shelby, Aslak Knutsen, Fabien Meurisse, Sjur Bakka, Xavier Coulon, Ari Kast, Dan Allen, Deniz Türkoglu, F. Meurisse, Jean-Charles Roger, Johannes Lehmann, allentc, Nikolay Tsankov, Chris Horne, Gabriel Mirea, Georg Ragaller, Harald Wellmann, klinger, Oliver Gondža, Stephen Crawley.

Why you might want to choose Ceylon

In a couple of days Ceylon 1.2 will be released. Ceylon 1.2 is out now, after a year of development. That’s exciting for us, but we think it would be interesting to summarize our thoughts about why you should be excited about Ceylon, and why you might consider it over other programming languages designed to run on the Java and JavaScript virtual machines.

Ceylon is a rather ambitious programming language, so sometimes when people ask us to summarize its advantages, it can be a bit difficult to know where to start!

A truly cross-platform language

Ceylon is a language that is equally at home on the JVM and on any JavaScript VM. Furthermore, implementation of a compiler backend for the Dart VM is already quite advanced, and work has begun on a native backend based on LLVM. The architecture of the compiler and tooling make addition of a new runtime environment, if not easy, at least straightforward.

Also important: Ceylon is rigorously defined by a complete language specification. The very act of writing a specification forces the language designer to really think through the semantics and corner cases, and acts as an essential reference as to what the behavior of the compiler should be.

Unlike other languages designed first for the JVM and then, belatedly, ported to JavaScript, Ceylon doesn’t feature JVM-specific numeric types with semantics that aren’t relevant to—or can’t be honored on—other VMs. And its language module isn’t polluted with dependencies to types like java.lang.Exception that don’t exist in other runtime environments. Instead, Ceylon’s language module completely abstracts the underlying runtime, and offers a set of elegant APIs that vastly improve on those available natively.

But this abstraction doesn’t come at the cost of lowest-common-denominator performance characteristics. On the contrary, you can expect Java-like performance when Ceylon executes on the JVM, Dart-like performance when Ceylon executes on the Dart VM, and JavaScript-like performance when Ceylon executes on a JavaScript VM like Node.js.

Nor does this abstraction limit the potential for interoperation with native code. Ceylon features excellent interoperation with Java and Maven, and it’s very easy to use most Java libraries in Ceylon. Similarly, Ceylon’s battery of dynamic constructs make interoperation with native JavaScript straightforward. With the new native functions and classes in Ceylon 1.2, it’s even possible to write a cross-platform module that interoperates with native Java and JavaScript code!

Finally, Ceylon's module system is already compatible with OSGi, with Maven, with Vert.x, with npm, and with requirejs. When Jigsaw finally arrives, we'll support it too.

This all amounts to an impressive engineering achievement, especially when taking into account the sophistication of the language itself.

Truly disciplined static typing

Chances are you have some experience writing code in a language with static typing. In combination with the right tooling, static typing makes code more robust, more understandable, and much more maintainable. But most languages go out of their way to include features and APIs which undermine their own static type system. A handful of languages such as ML and Haskell eschew this, offering a principled, disciplined approach to static typing. And programmers working with these languages report a curious thing: that their programs have a strange tendency to work first or second time they run them. Sure, it takes a little longer to produce a program that the compiler accepts, but once the compiler is satisfied, so many common bugs have already been eliminated, that the program is often already correct or at least almost correct.

Ceylon follows this same philosophy and, even though it’s a very different sort of language to ML, our experience is that our programs have the very same tendency to just work, almost immediately. You surely won’t believe this until you experience it yourself.

Ceylon’s type system itself is state-of-the-art, including generic types with both use-site and declaration-site covariance and contravariance, mixin (multiple) inheritance, principal instantiation inheritance, sum types, disjointness analysis, experimental support for higer-order and higher-rank generics, and, best of all, union and intersection types.

Union and intersection types

Ceylon was the first modern language to introduce a full implementation of union and intersection types, and the first language to realize that union and intersection types make flow-typing and type inference, especially generic type inference, very much simpler, more predictable, and more elegant. As of today, it’s still the language with the most sophisticated support for unions and intersections.

What’s most difficult to explain to folks who’ve never written any significant amount of code in Ceylon is just how powerful union and intersection types really are. Far from being an exotic construct that you encounter occasionally in tricky library code, they form an essential part of the everyday experience of writing code in Ceylon, and there is almost no significant Ceylon program which doesn’t use union types somewhere. It’s no exaggeration to say they will totally change the way you think about types.

Flow-sensitive typing

Ceylon was also the first modern language to embrace the notion of flow-sensitive typing, where the conditional logic in the body of a function affects the types inferred by the compiler. This approach eliminates a significant amount of verbosity and a significant number of runtime type casting errors. In combination with Ceylon’s powerful coverage and disjointness analysis, it also helps detect certain other kinds of logic errors.

The cleanest solution to the problem of null

The null value has perplexed language designers for decades. For a long time the “best” solution to the problem was an ML- or Haskell-style Maybe type, which offered type safety and eliminated the hated “null pointer” error. That solution works quite well for languages without subtyping, but it’s not the best approach for a language with subtyping and union types. Ceylon’s approach to null is just as type safe, but, with flow-sensitive typing, is more user-friendly, and it doesn’t require the overhead of separate Option-style wrapper objects. Unlike some other languages, Null isn’t a special case in Ceylon’s type system. Rather, it’s just one further example of the power of union types.

An elegant and powerful representation of tuple and function types

Like most other modern languages, Ceylon features tuples and function references. Unlike other languages Ceylon doesn’t represent these things as special cases in the type system, nor as an explosion of interface types, one for each tuple/function -arity. Instead, there is just one class Tuple, and one interface Callable which expresses function types in terms of tuple types. This approach allows the language to abstract over tuple or function -arity, and write higher-order functions that simply can’t be expressed in many other modern languages.

Reified generics

Along with Gosu, Ceylon is one of only two languages to offer reified generics on the JVM. And it’s the only language to offer reified generics on JavaScript virtual machines. You’ll sometimes run into attempts to “backsplain” the lack of reified generics in Java or other JVM languages: claims that reified generics perform badly, aren’t useful, or don’t work correctly with variance or with higher-order generics. Ceylon is an existence proof that none of these assertions is true.

The system of reified generics backs Ceylon's unique typesafe metamodel, which enables powerful runtime metaprogramming (reflection). It also makes debugging easier: you can see the runtime type arguments of every generic type or generic function in the Ceylon debugger!

A clean, efficient, highly readable syntax

A program written in idiomatic Ceylon is usually much more compact than the same program written in Java. But it’s also usually more readable. Ceylon’s syntax appears, at first glance, to be rather boring and conservative: it uses familiar (and cleaner) prefix type annotations instead of postfix types; it uses plain English keywords instead of cryptic strings of symbols; its naming convention eschews the use of abbreviations. When you look at a program written in Ceylon, you’ll probably understand more or less what it’s doing, even if you have only a passing familiarity with the language.

But that first impression misses a surprising amount of syntactic flexibility. Type inference, named arguments, comprehensions, tuples and sequences, “fat arrow” function definitions, anonymous functions, let, switch, if, case, and object expressions, flow-sensitive typing, and a very powerful facility for stream processing all work together to lend a level of expressiveness rarely seen outside of dynamic languages.

Operator overloading is a feature that, while useful, is widely abused in languages that offer it in unadulterated form. Instead of untrammelled operator overloading, Ceylon supports operator polymorphism, a more disciplined approach, where its operators have fixed, well-defined semantics, but are defined in terms of interfaces that user-written types may satisfy. Thus, you can have the + operator apply to your own class, but it always means some kind of addition-like operation.

We often don’t need to write down the types of things explicitly (type inference takes care of that), but when we do, Ceylon features a uniquely sophisticated expression syntax for types, along with type aliases, eliminating the verbosity of generic type declarations in other languages.

Awesome modularity and tooling

Ceylon’s suite of command line tools, including an incremental compiler, program runner, test runner, documentation compiler, code formatter, and WAR archive packager, are all accessible via the ceylon command, which features a plugin architecture, and excellent usability.

But Ceylon really comes into its own when authoring code using its powerful Eclipse-based IDE. Ceylon’s IDE has a feature set that easily surpasses other modern languages, and competes with much more mature languages like Java and C#. For those who prefer IntelliJ, an IntelliJ-based IDE is already in the works, with a first release planned in the next few months.

If you’ve ever spent time waiting for mvn to finish, you’ll immediately appreciate Ceylon’s powerful module architecture, which comes fully integrated into the command line tools and IDE. Modularity is a problem that is utterly central to the discipline of software engineering, and Ceylon, at least arguably, does it better than any other language out there. You don’t usually need to interact directly with the module repository architecture, since the command line compiler, IDE, and runtime all know how to fetch dependencies transparently.

Finally, Ceylon Herd is an fantastic tool that makes it ridiculously easy to share your work with other Ceylon developers.

Seamless web development

Ceylon is evolving into a compelling platform for web development, allowing you to reuse code written for a Java server on the JavaScript client and vice-versa, while still interoperating cleanly with native code on both sides. With our new serialization facility, you'll be able to transparently move objects between client and server. The Ceylon SDK comes with a HTTP server module based on Undertow, and a transactions module based on JBoss Transactions, but if you're looking for something "heavier", Ceylon modules can already be deployed on RedHat's OpenShift cloud platform, on WildFly, or on Vert.x 2 (with Vert.x 3 support coming soon). Alternatively you could use Ceylon in Node.js!

Developing web applications in Ceylon is actually enjoyable. With Ceylon, you'll spend much less time redeploying and/or refreshing things, or wrestling with inadequate tooling, and more time focused on writing elegant code.

A helpful and open-minded community

The Ceylon community is friendly and helpful, and you can usually get your questions answered quickly at just about any time of day. And we're extremely open to feedback, and don't mind explaining the reasoning behind decisions we've made. Of course we have the traditional mailing lists like any other open source project, but actually we're much more active on Gitter and on the GitHub issue tracking system. So come join us on our Gitter channel, and ask us anything!

If you're motivated to contribute, there's plenty of places to get involved. Some fairly significant parts of the Ceylon ecosystem were originally written by community contributors. Currently, the development of two new compiler backends is being driven by the community. Note that a whole lot of development, including the IntelliJ IDE, and the new compiler backends, is now being done in Ceylon, so you can learn Ceylon by contributing to the Ceylon project itself!