Personal opinion on software development in general, development methodologies, tools and best practices in particular

Archive for May, 2011

The Ceylon Project


A couple of weeks ago I heard about Ceylon for the first time. Ceylon is a new language which will run on the JVM, influenced by Java and Scala, and has many similarities with Java and C#.  The name comes from the island Sri Lanka, also called Ceylon, and from the Ceylon tea, so the link with Java is very obvious.
A compiler release is planned for late 2011.

Ceylon’s design goals include:

  • to be easy to learn and understand for Java and C# developers,
  • to eliminate some of Java’s verbosity, while retaining its readability,
  • to improve upon Java’s typesafety,
  • to provide a declarative syntax for expressing hierarchical information like user interface definition, externalized data, and system configuration, thereby eliminating Java’s dependence upon XML,
  • to support and encourage a more functional style of programming with immutable objects and higher-order functions, and
  • to provide built-in modularity.
The authors of Ceylon -Gavin King, inventor of Hibernate, is the lead of the Ceylon project- really like Java but it doesn’t evolve a lot anymore as a language which means that the suboptimalities won’t ever get fixed. C# has some solutions for these suboptimalities, it has delegates and Java still doesn’t have clojures, getters and setters are obsolete while Java still has them, … But on the other hand C# also has some suboptimalities that Java doesn’t have…
So the guys behind Ceylon wanted to create a language which combines the best of breeds.

No static methods

Ceylon doesn’t have Java-style static methods, but you can think of toplevel methods as filling the same role. The problem with static methods is that they break the block structure of the language. Ceylon has a very strict block structure — a nested block always has access to declarations in all containing blocks. This isn’t the case with Java’s static methods.

Method annotations

Ceylon has some annotations which can be used to describe a method. The doc annotation contains documentation that is included in the output of the Ceylon documentation compiler. The documentation compiler will support several other annotations, including by, for specifying the author of a program element, see, for referring to related code elements, and throws, for alerting the user to exception types thrown by an executable program element.

Example:

doc "The classic Hello World program" by "Gavin" see (goodbye) throws (IOException) void hello() { writeLine("Hello, World!"); }

There’s also a deprecated annotation for marking program elements that will be removed in a future version of the module.

Note that annotations like docbysee, and deprecated aren’t keywords. They’re just ordinary identifiers. The same is true for annotations which are part of the language definition: abstractvariableshared,formalactual, and friends. On the other hand, void is a keyword.

String template

There are two nice things about strings in Ceylon. The first is that we can split a string across multiple lines. That’s especially useful when we’re writing documentation in a doc annotation. The second is that we can interpolate expressions inside a string literal. Technically, a string with expressions in it isn’t really a literal anymore — it’s considered a string template.

An example of a  string template:

doc "The Hello World program ... version 1.1!" void hello() { writeLine("Hello, this is Ceylon " process.languageVersion " running on Java " process.javaVersion "!"); }

The only requirement is that the string template starts and ends with a string literal.

Dealing with objects that aren’t there

In Java we’re used to the fact that command line arguments only can be retrieved via the main method’s String array parameter. In Ceylon we can retrieve command line arguments via the process object which has an attribute named arguments, which holds a Sequence of the program’s command line arguments.

An example of an improved version of the original Hello World program:

doc "Print a personalized greeting" void hello() { String? name = process.arguments.first; String greeting; if (exists name) { greeting = "Hello, " name "!"; } else { greeting = "Hello, World!"; } writeLine(greeting); 
}

The local name is initialized with the first of these arguments, if any. This local is declared to have type String?, to indicate that it may contain a null value. Then the if (exists ...) control structure is used to initialize the value of the non-null local named greeting, interpolating the value of name into the message string whenever name is not null. Finally, the message is printed to the console.

Unlike Java, locals, parameters, and attributes that may contain null values must be explicitly declared as being of optional type. And unlike other languages with typesafe null value handling, an optional type in Ceylon is not an algebraic datatype that wraps the definite value. Instead, Ceylon uses an ad-hoc union type. The syntax T|S represents the union type of T and S, a type which may contain a value of type T or of type S. An optional type is any type of form Nothing|X where X is the type of the definite value. Fortunately, Ceylon lets us abbreviate the union type Nothing|X to X?.

To avoid non-null checks, default values can  be specified as a method parameter. Below you can find an example.

void hello(String name="World") { writeLine("Hello, " name "!"); }
 

There’s an even easier way to define greeting (cfr example above), using the ? operator. It’s just a little extra syntax sugar to save some keystrokes.

String greeting = "Hello, " + name?"World" + "!";

The ? operator returns its first argument if the first argument is not null, or its second argument otherwise. Its a more convenient way to handle null values in simple cases. It can even be chained:

String greeting = "Hello, " + nickName?name?"World" + "!";

The related ?. operator lets us call operations on optional types, always returning an optional type:

String shoutedGreeting = "HELLO, " + name?.uppercase?"WORLD" + "!";

Hiding implementation details

In Java and C#, a class controls the accessibility of its members using visibility modifier annotations, allowing the class to hide its internal implementation from other code. The visibility modifiers select between pre-defined, definite visibility levels like publicprotected, package private, and private. Ceylon provides just one annotation for access control. The key difference is that Ceylon’s shared annotation does not represent a single definite scope. Rather, its meaning is contextual, relative to the program element at which it appears. The shared annotation is in some cases more flexible, in certain cases less flexible, but almost always simpler and easier to use than the approach taken by Java and C#. And it’s a far better fit to a language like Ceylon with a regular, recursive block structure.

Members of a class are hidden from code outside the body of the class by default — only members explicitly annotated shared are visible to other toplevel types or methods, other compilation units, other packages, or other modules. A shared member is visible to any code to which the class itself is visible.

And, of course, a class itself may be hidden from other code. By default, a toplevel class is hidden from code outside the package in which the class is defined — only toplevel classes explicitly annotated shared are visible to other packages or modules. A shared toplevel class is visible to any code to which the package containing the class is visible.

Finally, a package may be hidden from packages in other modules. In fact, packages are hidden from code outside the module to which the package belongs by default — only explicitly shared packages are visible to other modules.

It’s not possible to create a shared toplevel class with package-private members. Members of a shared toplevel class must be either shared — in which case they’re visible outside the package containing the class, or un-shared — in which case they’re only visible to the class itself. Package-private functionality must be defined in un-shared (package-private) toplevel classes or interfaces. Likewise, a shared package can’t contain a module-private toplevel class. Module-private toplevel classes must belong to unshared (module-private) packages.

Ceylon doesn’t have anything like Java’s protected. The purpose of visibility rules and access control is to limit dependencies between code that is developed by different teams, maintained by different developers, or has different release cycles. From a software engineering point of view, any code element exposed to a different package or module is a dependency that must be managed. And a dependency is a dependency. It’s not any less of a dependency if the code to which the program element is exposed is in a subclass of the type which contains the program element!

Instantiating classes and overloading their initializer parameters

doc "Print a personalized greeting" void hello() { Hello(process.args.first).say(process.output); }

The rewritten hello() method above just creates a new instance of Hello, and invokes say(). Ceylon doesn’t need a new keyword to know when you’re instantiating a class.

I suppose you’re worried that if Ceylon classes don’t have constructors, then they also can’t have multiple constructors. Does that mean we can’t overload the initialization parameter list of a class?

I guess now’s as good a time as any to break some more bad news: Ceylon doesn’t support method overloading either! But, actually, this isn’t as bad as it sounds. The sad truth is that overloading is the source of various problems in Java, especially when generics come into play. And in Ceylon, we can emulate most non-evil uses of constructor or method overloading using:

  • defaulted parameters, to emulate the effect of overloading a method or class by arity (the number of parameters),
  • sequenced parameters, i.e. varargs, and
  • union types or enumerated type constraints, to emulate the effect of overloading a method or class by parameter type.

We’re not going to get into all the details of these workarounds right now, but here’s a quick example of each of the three techniques:

//defaulted parameter void print(String string = "\n") { writeLine(string); }
//sequenced parameter void print(String... strings) { for (String string in strings) { writeLine(string); } }
//union type void print(String|Named printable) { String string; switch (printable) case (is String) { string = printable; } case (is Named) { string = printable.name; } writeLine(string); }

Don’t worry if you don’t completely understand the third example just yet. Just think of it as a completely typesafe version of how you would write an overloaded operation in a dynamic language like Smalltalk, Python, or Ruby.

To be completely honest, there are some circumstances where this approach ends up slightly more awkward than Java-style overloading. But that’s a small price to pay for a language with clearer semantics, without nasty corner cases, that is ultimately more powerful.

Let’s overload Hello, and its say() method, using defaulted parameters:

doc "A command line greeting" class Hello(String? name = process.args.first) { ... doc "Print the greeting" shared void say(OutputStream stream = process.output) { stream.writeLine(greeting); } }

Our hello() method is now looking really simple:

doc "Print a personalized greeting" void hello() { Hello().say(); 
}

Conclusion

Ceylon looks like a great language which has a lot of similarities with Java and C#, so easy to learn. It tries to solve the suboptimalities of both languages, to remove overhead and to set good defaults. Though at this moment I don’t know if it will be a Java killer. But I’ll keep you informed guys! Stay tuned!