Team blog

Assertions in Ceylon

A distinguishing characteristic of Ceylon is that exceptions aren't used to represent programming errors. Well, that statement is a little vague or even over-broad, so let me make it more concrete with several examples of exceptions that I think always indicate a programming error in Java:

  • NullPointerException
  • ClassCastException
  • IndexOutOfBoundsException

The big problem with these exceptions is that they undermine the static type system.

Sure, all exceptions work around the type system—that's basically what we have exceptions for. And when you're talking about exceptions that represent transient conditions that can legitimately occur in a production system, and you're using the exception to transmit information from one part of the system (that can fail) to a completely different part of the system (that knows what to do in the case of failure) without forcing awareness of the failure onto every bit of intermediate code that occurs between these two bits of the system, then I think that's very reasonable.

But that's not the role of the exceptions above. These exceptions:

  • should never occur at runtime in a production system
  • represent problems that must be fixed by the programmer editing code
  • tend to hide the "corner" condition they represent from someone reading the code
  • are much too low-level to carry any useful information about the real problem

Instead, Ceylon tries to encode these "corner" conditions into the type system. The compiler won't let you write:

print(process.arguments[1].uppercased);

This code isn't well-typed because process.arguments[1] is of type String?, reflecting the fact that there might not be a second element in the list process.arguments.

Instead you're forced to at least take into account the possibility that there are less than two arguments:

if (exists arg = process.arguments[1]) {
    print(arg.uppercased);
}
else {
    throw Exception("missing second argument");
}

I know some of you reading this are itching to condemn Ceylon for making you write three lines of code instead of one. But, as I find myself repeating over and over, we spend much more time reading other people's code than we spend writing our own code, and the second code example makes it much clearer to the reader that there is another case that needs to be taken into account. Furthermore the exception carries much more information about what went wrong than an IndexOutOfBoundsException would.

But what if, ask my doubters, I already know that there is more than one argument? What if my code looks like this:

if (process.arguments.size>=3) {
    if (exists arg = process.arguments[1]) {
        print(arg.uppercased);
    }
    else {
        throw Exception("missing second argument");
    }
}

In this case, it's clear that the exception can never occur. I'm forced to write code that simply never executes at runtime, under any scenario!

Well, that's a great point, and it's exactly why we've introduced the assert statement in M4. An assertion failure, unlike an exception, always represents a programming error—in production code, an assertion should never fail.

Now, sure, assertion failures are represented by a kind of exception, but unlike NullPointerException or IndexOutOfBoundsException, this exception is much more likely to carry useful information about the cause of the failure. And the assertion helps document the assumptions made by the code, for the benefit of people coming along and reading it later.

We can rewrite the example above like this:

if (process.arguments.size>=3) {
    assert(exists arg = process.arguments[1]);
    print(arg.uppercased);
}

Or even like this:

value arg = process.arguments[1];
if (process.arguments.size>=3) {
    assert(exists arg);
    print(arg.uppercased);
}

You can assert an exists, nonempty, is, or boolean condition, all the same options you have with if or while.

Object person = ... ;
assert (is Person person);
print(person.name);

Note that this is a lot like a traditional Java-style typecast, but the syntax reflects a much more disciplined approach to the problem.

You're encouraged to add some extra information to document the assertion:

value arg = process.arguments[1];
if (process.arguments.size>=3) {
    doc "second argument must be provided"
    assert(exists arg);
    print(arg.uppercased);
}

The documentation appears in the assertion failure message.

This documentation becomes especially useful if start using Extract Function to refactor this code:

void printSecondArg(String? arg) {
    doc "second argument must be provided"
    assert(exists arg);
    print(arg);
}

value arg = process.arguments[1];
if (process.arguments.size>=3) {
    printSecondArg(arg);
}

In future, the Ceylon documentation tool will automatically include assertions like this about method parameter values in a list of method preconditions.

A single assert statement may assert multiple conditions, for example:

value first = process.arguments[0];
value second = process.arguments[1];
if (process.arguments.size>=3) {
    assert(exists first, exists second);
    print(first + ", " + second);
}

For the record, by popular demand, Ceylon M4 even lets us include multiple conditions in an if or while statement:

if (exists first = process.arguments[0],
    exists second = process.arguments[1]) {
    print(first + ", " + second);
}

Ceylon M4 and Ceylon IDE M4 released!

Ceylon M4 "Analytical Engine" is now available for download, along with a simultaneous compatible release of Ceylon IDE. The compiler now implements almost all of the language specification, for both Java and JavaScript virtual machines as execution environments. New Ceylon platform modules are available in Ceylon Herd, the community module repository.

You can download the Ceylon command line distribution here:

http://ceylon-lang.org/download

Or you can install Ceylon IDE from Eclipse Marketplace or from our Eclipse update site.

Ceylon M4 and Ceylon IDE M4 require Java 7.

The Ceylon team hopes to release Ceylon 1.0 beta in January.

Language features

M4 is an almost-complete implementation of the Ceylon language, including the following new features compared to M3:

The following language features are not yet supported in M4:

  • reified generics
  • user-defined annotations, interceptors, and the type safe metamodel
  • serialization

This page provides a quick introduction to the language. The draft language specification is the complete definition.

Ceylon IDE

Ceylon IDE is a complete development environment for Ceylon, based on the Eclipse platform. This release of Ceylon IDE introduces:

  • support for JavaScript compilation and execution,
  • module import completion,
  • the brand new Ceylon Repository Explorer view,
  • support for Maven repositories, and
  • more than 30 other improvements and bug fixes.

Ceylon IDE M4 requires Java 7. Users of Ceylon IDE on Mac OS should install Eclipse Juno. Users on other platforms may run Ceylon IDE in either Eclipse Indigo or Eclipse Juno on Java 7. Ceylon IDE will not work if Eclipse is run on Java 6.

Compilation and execution on the JVM and JavaScript VMs

The Ceylon command-line tools and IDE both support compilation to either or both of the JVM or JavaScript.

Ceylon programs compiled to JavaScript execute on standard JavaScript virtual machines. The Ceylon command line distribution and IDE include a launcher for running Ceylon programs on Node.js.

Interoperation with Java

Interoperation with Java code is robust and well-tested. As usual, this release fixes several bugs that affected Java interoperation in the previous release.

Contrary to previous releases, the JDK is no longer automatically imported, so you need to import the JDK using the modularised JDK module names as defined by Jigsaw (Java 9).

Platform modules

The following new platform modules are now available in Ceylon Herd:

  • ceylon.net provides URI and HTTP support
  • ceylon.io provides charset and socket (blocking and non-blocking) support
  • ceylon.json provides JSON parsing and serialization
  • ceylon.collection collection implementations organized into mutable array-based collections, mutable hashtable-based collections and immutable linked-list based collections

The language module, ceylon.language is included in the distribution.

Modularity and runtime

The toolset and runtime for Ceylon are based around .car module archives and module repositories. The runtime supports a modular, peer-to-peer class loading architecture, with full support for module versioning and multiple repositories, including support for local and remote module repositories, using the local file system, HTTP, WebDAV, or even Maven repositories for interoperation with Java.

The shared community repository, Ceylon Herd is now online:

https://herd.ceylon-lang.org

Source code

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

https://github.com/ceylon

Issues

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

Community

The Ceylon community site includes documentation, the current draft of the language specification, the roadmap, and information about getting involved.

http://ceylon-lang.org

Acknowledgement

We're deeply indebted 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 this release:

Gavin King, Stéphane Épardaud, Tako Schotanus, Emmanuel Bernard, Tom Bentley, Aleš Justin, David Festal, Flavio Oliveri, Max Rydahl Andersen, Mladen Turk, James Cobb, Tomáš Hradec, Michael Brackx, Ross Tate, Ivo Kasiuk, Enrique Zamudio, Julien Ponge, Julien Viet, 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, Paco Soberón, sjur, Xavier Coulon.

At devoxx 2012? Come write Ceylon code for real, challenge yourself!

Feel like learning Ceylon but you don´t know how to get started? You want to start contributing to Ceylon?

Don´t worry, we´ve got you covered. Gavin, Emmanuel and I will be at Devoxx Belgium 2012 for two important gigs.

Hands-On Lab on Monday 12th of November

This is the best place to feel what Ceylon is like.

On Monday 12th of November we are holding a Hands-On Lab where you will write your first Ceylon programs with a series of guided exercises. Come with your laptop, we will help you install the Ceylon IDE. This should be interesting, after all: the language authors and implementers will be there to answer your every question.

HackerGarten on Tuesday 13th of November

This is the best place to start contributing.

On Tuesday 13th of November we will be holding a full-day HackerGarten with the rest of the JBoss team. Here we will not only help you get started with coding in Ceylon but also help you get started with contributing to the Ceylon project, and its many sub-projects.

If you want to contribute but don´t know how to get started, Gavin, Emmanuel and I will be there to guide you. And contrary to popular belief, none of us have bitten anyone in the last... four months at least! Come with your laptop, that would not be fun otherwise.

Some planning required

Make sure to register for the University part of Devoxx: there are still tickets left. Also remember to bring your laptop for both those occasions.

Install JDK 7. We´ll provide everything else required via WiFi or USB keys.

Destructuring considered harmful

A language is said to feature destructuring if it provides a syntax for quickly declaring multiple local variables and assigning their values from the attributes of some complex object. For example, in Ceylon, we let you write:

for (k->v in map) { ... }

This is a simple kind of destructuring where the key and item attributes of the map Entry are assigned to the locals k and v.

Let's see a couple more examples of destructuring, written in a hypothetical Ceylon-like language, before we get to the main point of this post.

A number of languages support a kind of parallel assignment syntax for destructuring tuples. In our hypothetical language, it might look like this:

String name, Value val = namedValues[i];

Some languages support a kind of destructuring that is so powerful that it's referred to as pattern matching. In our language we might support pattern matching in switch statements, using a syntax something like this:

Person|Org identity = getIdentityFromSomewhere();
switch (identity)
case (Person(name, age, ...)) {
    print("Person");
    print("Name: " + name);
    print("Age: " + age);
}
case (Org(legalName, ...)) {
    print("Organization");
    print("Name: " + legalName);
}

Now, I've always had a bit of a soft spot for destructuring—it's a minor convenience, but there are certainly cases (like iterating the entries of a map) where I think it improves the code. A future version of Ceylon might feature a lot more support for destructuring, but there are several reasons why I'm not especially enthusiastic about the idea. I'm going to describe just one of them.

Let's start with the "pattern matching" example above. And let's stipulate that I—perhaps more than most developers—rely almost completely on my IDE to write my code for me. I use Extract Value, Extract Function, Assign To Local, Rename, ⌘1, etc, in Ceylon IDE like it's a nervous tic. So of course the first thing I want to do when I see code like the above is to run Extract Function on the two branches, resulting in:

Person|Org identity = getIdentityFromSomewhere();
switch (identity)
case (Person(name, age, ...)) {
    printPerson(name, age);
}
case (Org(legalName, ...)) {
    printOrg(legalName);
}

...

void printPerson(String name, Integer age) {
    print("Person");
    print("Name: " + name);
    print("Age: " + age);
}

void printOrg(String legalName) {
    print("Organization");
    print("Name: " + legalName);
}

Ooops. Immediately we have a problem. The schema of Person and Org is smeared out over the signatures of printPerson() and printOrg(). This makes the code much more vulnerable to changes to the schema of Person or Org, makes the code more vulnerable to changes to the internal implementation of these methods (if we want to also print the Person's address, we need to add a parameter), and it even makes the code less typesafe. The problem gets worse and worse as I recursively run Extract Value and Extract Function on the implementation of printPerson() and printOrg().

Now consider what we would get without the use of destructuring, as we would do in Ceylon today. We would have started with:

Person|Org identity = getIdentityFromSomewhere();
switch (identity)
case (is Person) {
    print("Person");
    print("Name: " + identity.name);
    print("Age: " + identity.age);
}
case (is Org) {
    print("Organization");
    print("Name: " + identity.legalName);
}

Whether this is better or worse than the code using of pattern matching is somewhat in the eye of the beholder, but clearly it's not much worse and is arguably even a little cleaner. Now let's run Extract Function on it. We get:

Person|Org identity = getIdentityFromSomewhere();
switch (identity)
case (is Person) {
    printPerson(identity);
}
case (is Org) {
    printOrg(identity);
}

...

void printPerson(Person identity) {
    print("Person");
    print("Name: " + identity.name);
    print("Age: " + identity.age);
}

void printOrg(Organization identity) {
    print("Organization");
    print("Name: " + identity.legalName);
}

I think it's very clear that this a much better end result. And I hope it's also clear that this is in no way a contrived example. The arguments I'm making here scale to most uses of pattern matching. The problem here is that introducing local variables too "early" screws things up for refactoring tools.

Essentially the same argument applies to tuples: a tuple seems like a convenient thing to use when you "just" have a quick helper function that returns two values. But after a few iterations of Extract Function/Extract Value, you wind up with five functions with the tuple type (String, Value) smeared out all over the place, resulting in code that is significantly more brittle than it would have been with a NamedValue class.

I've repeatedly heard the complain that "oh but sometimes it's just not worth writing a whole class to represent the return value of one function". I think this overlooks the effect of code growing and evolving and being refactored. And it also presupposes that writing a class is a pain, as it is in Java. But in Ceylon writing a class is easy—indeed, it looks just like a function! Instead of this:

(String, Value) getNamedValue(String name) {
    return (name, findValueForName(name));
}

we can just write this:

class NamedValue(name) {
    shared String name;
    shared Value val = findValueForName(name);
}

No constructor, no getters/setters, and if this is a member of another class, you can just annotate it shared default, and it's even polymorphic, meaning that there is not even a need to write a factory method. And this solution comes with the huge advantage that the schema of a NamedValue is localized in just one place, and won't start to "smear out" as your codebase grows and evolves.

Ceylon IDE M3.2 released

Gavin, Sjur and Tomáš have been hard at work all summer to deliver a much-improved version of the Ceylon IDE. This release support exactly the same language constructs and features the same JVM backend compiler as the M3.1 release, so it's 100% compatible with it. But while the backend is the same, the UI is much improved, as you can see from the following change log and screen shots.

No more dependence on IMP

We recommend removing IMP from your Eclipse installation.

Ceylon Explorer view

The Ceylon Explorer View presents packages as children of the containing modules. You should reset your Ceylon Perspective after updating the IDE on order to get the Ceylon Explorer in place of JDT's Package Explorer.

Awesome documentation hover

Hover over the name of a Ceylon or Java declaration to see the gorgeous new hover info. Click in it to browse to other related declarations.

Code popup

Ctrl-P, or "Show Code" pops up an information control containing the code of the referenced declaration. Ctrl-P again opens an editor. Hyperlink navigation from the Code popup is also possible.

Hierarchy and Outline popups

As before, Ctrl-T and Ctrl-O open the popup Hierarchy and Outline views. These now look a lot prettier, and they are also more functional. You can filter the contents by typing inside them, and they remember their bounds. The Hierarchy View is also much better integrated with Java stuff. Ctrl-T inside the open Hierarchy View cycles between Hierarchy View, Supertypes View, and Subtypes View. These popups have configurable background colors.

Auto-addition of needed imports

Refactorings, quick fixes, and autocompletions now add any needed imports to the top of the source file. In addition, autocompletion will propose declarations from any imported module, even if the package is not imported in the current unit.

New Quick Fixes/Assists

Thanks to Sjur and Tomáš, we now have some new quick fixes and assists:

  • convert if/else to then/else, then/else to if/else
  • invert if/else
  • add type constraint to type parameter

Clean Imports

Clean Imports now automatically fills in missing imports if unambiguous, and prompts the user to choose a declaration to import if ambiguous.

Terminate Statement

Ctrl-2 adds any necessary semicolon, closing parens, and closing braces to terminate the statement at the current line. This feature is almost magical in how good it is at guessing where the closing punctuation needs to go.

Mark occurrences

Occurrence marking is now enabled by default and works like JDT (the declaration to highlight is the one under the caret).

Proposals popups

Completions proposals and quick fix/assist proposal popups now have syntax highlighting. Typing a "." automatically activates the completion proposal window. When only one completion proposal is available, it is automatically applied.

Select Enclosing/Restore Previous Selection

Ctrl-shift-up expands the selection to the containing expression or program element. Ctrl-shift-down takes you back to the previous selection.

Also Ctrl-Shift-P takes you to the matching bracket and Show In now works.

Outline View

There is now a popup context menu on Outline View nodes. The selected node now follows the editor caret location.

Tomáš added a toolbar to the Outline View, letting you filter out local declarations and expand/collapse all nodes.

Open Ceylon Declaration / Open Type

The default keyboard binding for Open Ceylon Declaration is now Ctrl-Shift-O instead of Ctrl-Shift-C. This is more ergonomic and works even inside the Java editor. Inside the Ceylon perspective, Ctrl-Shift-T opens the Open Type dialog, which lets you choose between Ceylon and Java types in one dialog (a very slow-to-load dialog, but hey...).

F3 (Open Selected Declaration) now handles Java declarations.

Support for multiple module repositories

The New Project wizard and Ceylon Preferences page now let you configure multiple module repositories for a project.

Backend errors

Errors from the compiler backend now show up in the Problems View and vertical ruler, and compiler progress is visible in the Progress View and status bar. The 80s-style scrolling console is gone.

Progress

Building the completion proposals and hierarchy for the first time is now expensive because it involves trawling the whole JDK. You get a nice progress bar in the status bar of the main window.

Parser lifecycle

Code is lexed/parsed synchronously on every keypress, and lexed/parsed+typechecked synchronously on every keypress when the completions proposals window is open. This results in much more responsive syntax highlighting and fixes some wierd behavior in the completion proposals window. When the completions proposal window is not open, typechecking still happens in a background job as before.

Download it today

The Ceylon IDE is available today from our update site or from Eclipse Marketplace:

Ceylon IDE Eclipse Indigo drag and drop into a running Eclipse workspace

After installing, go to Help > Welcome to Ceylon to get started.