Ceylon is heavily influenced by
Java's syntax, but adds many new features.
Type system One of the most novel aspects of Ceylon compared to Java is its
type system. Ceylon foregoes Java's primitive types and
boxing in favor of a type system composed entirely of first-class objects. While this may cause boxing overhead in some situations, it makes the type system more uniform. Ceylon allows for union and
intersection types, in a similar fashion to
TypeScript,
Whiley and
FLOW, which in fact, took the idea from Ceylon. Union types, written A|B, allow a variable to have more than one type. The following example shows a Ceylon function which may take either an
integer or a
string: shared void integerOrString(Integer|String input) { if (is Integer input) { print("Got the integer ``input``"); } else { print("Got the string '``input``'"); } } Intersection types, written A&B, are the theoretical foundation of
flow-sensitive typing: shared void integerOrString(Integer|String input) { Integer added = input + 6; // illegal; the + operator is not defined on Integer|String if (is Integer input) { Integer added = input + 6; // legal; input is now known to be an Integer print("Got the integer ``input``"); } else { print("Got the string '``input``'"); } } The condition is Integer input narrows the type of input to <Integer|String> & Integer, which
distributes to Integer&Integer | String&Integer, which, as String and Integer are disjoint types, is equivalent to Integer&Integer | Nothing (Nothing is the empty bottom type), which simplifies to just Integer.
Null safety Union and intersection types are used to provide
null safety. The top type of the Ceylon type hierarchy is the class Anything, which has two subclasses: Object, the superclass of all normal classes and all interfaces, and Null, with the only instance
null. Since Object and Null are disjoint types, most regular types like Integer or List<String> are not nullable; a
nullable type is the union Integer|Null, abbreviated Integer?. Intersection types can be used to get a non-optional type out of a possibly-optional type, such as a type parameter. For example, the signature of a function that removes null elements from a stream of values could be: Iterable removeNulls(Iterable stream); When removeNulls is called with a stream of Integer|Null elements, the result will be a stream of <Integer|Null> & Object elements, which simplifies to Integer.
Functions Similarly to many modern languages, Ceylon supports
first class functions and
higher order functions, including function types and
anonymous functions // A top-level higher-order function using block syntax (not associated with any user-created classes) String process(String text, String transformString(String toChange)) { return transformString(text); } // A top-level function calling String.reverse in expression form. String reverse(String text) => text.reversed; // A function reference to String.reversed but mostly equivalent to the function above. String(String) reverseFunctionReference = String.reversed; // An example where the top-level function above is provided as an argument to the higher-order function above String reversed1 = process("one", reverse); // An example where an anonymous function - (text) => text+text - is provided to the higher-order function above. String reversed2 = process("one", (text) => text+text);
Enumerated types Similar to Java and many other languages, and with a similar mechanism as
algebraic types, Ceylon supports
enumerated types, otherwise known as enums. This is implemented in Ceylon with a pattern of limiting the instances of an abstract class at declaration to a limited set of objects (in this case, singleton instances). Another way to implement this pattern is with the new constructor feature in Ceylon 1.2 where the objects are implemented as different named constructor declarations. // Traditional syntax for enumerated type, in this case, limiting the instances to three objects(for this purpose: Singletons) abstract class Vehicle(shared String name) of plane | train | automobile {} object plane extends Vehicle("plane") {} object train extends Vehicle("train") {} object automobile extends Vehicle("automobile") {} // Compile error: type is not a subtype of any case of enumerated supertype: 'boat' inherits 'Vehicle' //object boat extends Vehicle("boat") {} // New (as of Ceylon 1.2.0) constructor-based syntax class Vehicle of plane | train | automobile { String name; abstract new named(String pName) { name = pName; } shared new plane extends named("plane") {} shared new train extends named("train") {} shared new automobile extends named("automobile") {} // Compile error: value constructor does not occur in of clause of non-abstract enumerated class: 'boat' is not listed in the of clause of 'Vehicle' //shared new boat extends named("boat") {} }
Type inference Ceylon is strongly and statically typed, but also has support for
type inference. The value keyword is used to infer the type of a variable, and the function keyword is used to infer the type of a function. The following two definition pairs are each equivalent: Integer i = 3; value i = 3; Integer add(Integer i1, Integer i2) { return i1 + i2; } function add(Integer i1, Integer i2) { return i1 + i2; } However, to make single-pass type inference possible, type inference is only allowed for non-toplevel and unshared declarations.
Entry point with names By default the starter (ceylon run) runs the shared run() function of a module: /* The classic Hello World program */ shared void run() { print("Hello, World!"); } but any other shared function without parameters can be used as main calling the program with the
run parameter, like this: ceylon run --compile=force --run hello default == Versions ==