Team blog

Ceylon 1.2.1 is now available

Three months after the last major release, Ceylon 1.2.1 is a new maintenance release, with almost 100 issues closed, including new features, improvements and bug fixes such as:

  • you can now iterate java.lang.Iterable values in for statements and use java.lang.AutoCloseable values in try statements,
  • support for Java 9 and Jigsaw modules,
  • experimental support for type functions on the JVM,
  • reduced run-time dependencies for your Ceylon program,
  • better interoperation with JavaScript arrays,
  • better compatibility with previous and future Ceylon releases.

Note that for the JVM backend, this release is backwards-compatible with the previous major release (1.2.0), which means you can use modules compiled with 1.2.0 on a 1.2.1 distribution out of the box. This is not as easy the other way around, if you want to run modules compiled for 1.2.1 on a 1.2.0 distribution, which is why we recommend you upgrade to 1.2.1.

Sadly, on the JavaScript backend, we had to break binary compatibility to fix serious interoperation issues, and so modules compiled for 1.2.1 and 1.2.0 are not compatible. We recommend you upgrade your distribution to 1.2.1 and recompile your modules.

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.

IDE

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

  • improved documentation hover,
  • better UI responsiveness,
  • support running on Java 9.

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.1, are available in the shared community repository, Ceylon Herd.

This release introduces several new platform modules:

  • ceylon.numeric is a cross-platform module containing math operations for Integer and Float. In time it will replace parts of the JVM-only ceylon.math module.
  • ceylon.decimal is a JVM-only module (but soon to be cross-platform) containing arbitrary-length decimal support. In time it will replace parts of the JVM-only ceylon.math module.
  • ceylon.whole is a cross-platform module containing arbitrary-length integer support. In time it will replace parts of the JVM-only ceylon.math module.
  • ceylon.random is a cross-platform module containing random number generators. In time it will replace parts of the JVM-only ceylon.math module.
  • ceylon.interop.browser contains JavaScript-only interoperation functions and types for the DOM, HTML and XMLHttpRequest.

Along with several API enhancements and bugfixes, including:

  • Many new features for ceylon.test, the Ceylon Test Suite.
  • Performance improvement of the ceylon.json parser.

Web IDE

You can try Ceylon using the Web IDE, 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.2.0

Migration from Ceylon 1.2.0 is easy. To recompile a module for 1.2.1:

  • 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.2.0" to "1.2.1" .
  • If it was compiled against Ceylon 1.2.0 you should still be able to use it in 1.2.1 for the JVM backend, as it is backwards-compatible. Sadly, this is not the case for the JavaScript backend, and so you will need to recompile your modules with 1.2.1.

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, Byron Clark, Francisco Reverbel, Jonas Berlin, Luke Hutchison, Nikita Ostroumov, Santiago Rodriguez, Sean Flanigan.

Ceylon on Java 9 + Jigsaw

Everyone is talking about modules these days. New languages try to incorporate them, and older languages try to retrofit them in. Which is great news, because modules are essential. Java 9 is around the corner, because it's supposed to come out next year, and the really big new feature is modularity, which it calls the Jigsaw project.

Ceylon is a language that featured modularity from the start, as part of the language and not as an afterthought requiring complex third-party tool integration. In fact, at the time we designed our Java JDK integration (at the time of Java 7), we went as far as using the Jigsaw modularity plans for the JDK (yes Jigsaw got delayed a few times) from the start, requiring JDK users to import Jigsaw modules as they were planned at the time, rather than import the whole JDK in one go. So perhaps we were the first ones with a modular JDK, in some sense :)

Java 9’s Jigsaw

Jigsaw is a very large project, which includes the following changes:

  • Modularisation of the JDK into smaller units, such as java.base, java.xml that Ceylon users of the JDK are already familiar with.
  • This modularisation means removal of rt.jar that contained every JDK class. In fact it's been replaced by a bootmodules.jimage file which is not a jar, but whose contents can be accessed by a virtual NIO FileSystem at jrt:/.
  • You can write your own modules. To turn your Java code into a Java 9 module, you simply add a module descriptor in a file called module-info.java (much like Ceylon module descriptors, or Java package descriptors), which describes your module and the Java 9 compiler and jar tools will then generate a jar with a module-info.class descriptor at the root of the jar.
  • That module descriptor allows you to specify the module name, the packages it exports, the name of the modules it imports and a few other things. But not versions, unfortunately, which are currently "out of scope" in Java 9.
  • You can run your code as previously from the classpath, or as modules from the module path. The module path is just a folder in which you can place your modules and the JRE will look them up for you based on module name alone.

Ceylon and Jigsaw

Java 9 has two early-access (EA) downloads for users to try the module system. Only one of them includes user modules. Make sure you use that one if you want to try out Ceylon running on Java 9.

Over the past weeks I've worked on getting Ceylon compiling and running on Java 9. This involved (among other details) the following things:

  • Generating module-info.class files from Ceylon module descriptors.
  • Generating module-info.class files for the Ceylon distribution modules which are not written in Ceylon (like the compilers or runtime system).
  • Making use of the Java 9 module descriptors for the shared packages information it contains (something supported by Ceylon since the beginning, but which was lacking for plain Java jars).
  • Backporting Java 9 code that deals with modules to the javac fork we use to compile Java files and generate bytecode.
  • Dealing with the removal of rt.jar and the boot classpath.
  • Creating a new tool ceylon jigsaw which allows for the creation of a Java 9 module path.
  • Making sure we can run Ceylon modules as Java 9 modules as an alternative to the four existing JVM runtimes which are the JBoss Modules, classpath, OSGi or Java EE.
  • Make sure we can build and run on any of Java 7,8,9. This means that by default we do not generate Java 9 module descriptors, because several tools have problems dealing with them at this time.
  • We have split some things out of the ceylon.language module so that it no longer depends on the compilers and type-checker, which means a lighter minimal runtime, which will be even further improved in the next weeks with more dependency removals :)

Just tell me how to try this!

I will spare you the many details of this work, but with help from the Java 9 team, this is how you can run your Ceylon modules on a Java 9 runtime:

  • Download the Java 9 EA with Jigsaw.
  • Get the Ceylon distribution code, and compile it with ant -Djigsaw=true clean dist to get the Java 9 module descriptors.
  • Write your Ceylon module normally, but compile it with .../ceylon/dist/dist/bin/ceylon compile --generate-module-info to generate the Java 9 module descriptors.
  • Create your Java 9 module path in an mlib folder with .../ceylon/dist/dist/bin/ceylon jigsaw create-mlib my.module/1.
  • Run your Ceylon module on Java 9 with .../jdk1.9.0-jigsaw/bin/java -mp mlib -m ceylon.language my.module/1. At the moment, the ceylon.language module acts as main module and does the required setting up of the Ceylon runtime before loading and invoking your Ceylon module.

That's all there is to it!

Caveats

Java 9 is not complete yet, and our support for Java 9 is also not complete. There will be issues and bugs, and in fact we already know of several limitations, such as the following:

  • While you can import a pure Java 9 module from Ceylon, we will respect its exported packages, but we will not respect its dependencies, because Java 9 modules do not include dependency versions. In fact, even the module's version is not stored in the source module descriptor, but added by an optional flag to the Java 9 jar tool. Ceylon requires module dependencies to describe a version, so we have to combine the Java 9 module descriptor with another descriptor such as an OSGi descriptor or a Maven pom.xml descriptor. This merging of information is not currently done.
  • Java 9 does not currently support optional modules or module cycles. It is not clear if they will support them at this time, unfortunately.
  • The ceylon import-jar tool may complain about module visibility artifacts. We intend to fix this in time, but for now you can use --force.
  • The JDK module list we used in Ceylon has slightly changed in Java 9. This is what we get for being the first to support Jigsaw ;) For example, the javax.xml module has been renamed to java.xml. We have set up aliases so that it "just" works, but there are modules that have been merged, and packages that have changed module, so it will not always work.
  • The Java 9 runtime has been tested, but not as thoroughly as the existing JBoss Modules, classpath, OSGi or Java EE runtimes. We expect a few issues in the Ceylon metamodel.

First maintenance update for the Ceylon IDE version 1.2.0

Introducing the first Ceylon IDE maintenance update

Just after the release of Ceylon 1.2.0, we started managing a maintenance branch of the IDE-related code, that will always be fully compatible with the last release of the Ceylon (Command line distribution and SDK).

This way we've already been able to very easily backport to this branch many bug fixes or enhancements implemented on the main development branch.

As a result, the first maintenance update of the Ceylon IDE plugin for Eclipse, which fixes about 30 issues, has just been published onto the main Ceylon IDE update site.

So if you currently use the version 1.2.0 of the Ceylon IDE Eclipse plugin, simply run the Check For Updates command in the Eclipse Help menu, and Eclipse should propose you to update the Ceylon IDE features.

And if you still don't use the Ceylon IDE Eclipse plugin, just install it and give it a try now.

Source tracking

Of course each maintenance update will be tagged inside the GitHub source repositories.

So as soon as we know the version of the Ceylon IDE feature, visible from the Eclipse About dialog:

we can immediately checkout the precise source code corresponding to a Ceylon IDE installation.

This will help us reproducing more easily, and fixing more quickly, any issue encountered on the production Ceylon IDE.

Maintenance update release notes

Detailed release notes of this maintenance update can be found here, and related commits can be found here.

Issues

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

About Ceylon

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.

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.

Troll alert

This is a rather unusual blog entry, one I was not thinking we'd ever have to write, but after a long debate, we've decided to go public about certain events that have happened to the Ceylon community.

We are being aggressively harassed by a Troll for a month now, which is taking the following forms:

  • Logging on our Gitter channels, insulting and threatening users, then renaming his GitHub account so we cannot find/ban him.
  • Using fake identities to ask us for help on Gitter for hours on end, only to reveal later he was the troll and resume insults/threats.
  • Impersonating Gitter users to make them look bad.
  • Spreading lies about Ceylon on other JVM languages discussion channels.
  • Using sock puppet accounts on reddit to turn many threads into Ceylon bashing or ad-hominem attacks on Ceylon team members.
  • Defacing the Ceylon Wikipedia page [Edit: it's not clear yet, it could be a honest mistake on both parts].
  • And other things I cannot reveal here.

Since I have no doubt at all our troll is reading this, you will understand I am not revealing all the information that we have on what he's done and who he is. In fact it's a pretty difficult exercise.

I also don't want to explicitly name his identities here because I don't believe in mob justice, I hope you will understand that.

Naturally we've tried many things to make sure he stops poisoning our community, and are still working on measures behind the scenes, but his impact cannot be ignored.

Although we've always tried to follow the usual Troll-avoidance strategy of ignoring him, we have many people in the Ceylon community who see these events and wonder what's going on. They don't understand why every new user is suspect (our troll uses about two new fake accounts per day), or why suddenly a seemingly sane person will start throwing insults at everyone. The more we talk to him, and the more we talk about him, the more persisting he becomes, so we don't want to make it worse.

But there has to be an explanation we can point to when people ask what the hell is happening, so this is it.

We have a toxic person in our community, we haven't been able to get rid of him no matter how many times we asked him, and we don't have the tools to prevent him from harassing us, and he knows it. What goes on in his mind is beyond our ability to understand, but the fact is he exists, and seems driven by some motive to spend hours every day annoying us.

We are, again, withholding information about him and about the various things we are doing behind the scenes to deal with him, but rest assured we are not sitting idle.

That's pretty much as I can say at this point. If you see someone throwing insults at people on our Gitter channel, this is not something we condone at all, and we keep banning his new accounts for that, so don't let our apparent lack of reaction (besides the banning) fool you into thinking that we're silently condoning his attitude. We're not.

Thanks for your understanding.