Sie sind auf Seite 1von 103

Ceylon Language

Source: http://relation.to/tag/Introduction+to+Ceylon

10/19/2011

1.

BASICS ........................................................................................................................ 5

ABOUT CEYLON ........................................................................................................................... 5 WRITING A SIMPLE PROGRAM .......................................................................................................... 5

..................................................................................................... 7 STRINGS AND STRING INTERPOLATION ................................................................................................. 8 DEALING WITH OBJECTS THAT AREN'T THERE .......................................................................................... 9 OPERATORS FOR HANDLING NULL VALUES .......................................................................................... 11 DEFAULTED PARAMETERS ............................................................................................................. 11 2. CLASSES AND MEMBERS............................................................................................ 12 CREATING YOUR OWN CLASSES ....................................................................................................... 12 HIDING IMPLEMENTATION DETAILS .................................................................................................. 13 ABSTRACTING STATE USING ATTRIBUTES ............................................................................................ 14 UNDERSTANDING OBJECT INITIALIZATION ........................................................................................... 15 INSTANTIATING CLASSES AND OVERLOADING THEIR INITIALIZER PARAMETERS ................................................. 16 3. INHERITANCE AND INTRODUCTION ........................................................................... 19 INHERITANCE AND REFINEMENT ...................................................................................................... 19 ABSTRACT CLASSES ..................................................................................................................... 20 INTERFACES AND MIXIN INHERITANCE ............................................................................................... 21 AMBIGUITIES IN MIXIN INHERITANCE ................................................................................................ 23 INTRODUCTION ......................................................................................................................... 25 INTRODUCTION COMPARED TO EXTENSION METHODS AND IMPLICIT TYPE CONVERSIONS .................................... 27 TYPE ALIASES ............................................................................................................................ 28 MEMBER CLASSES AND MEMBER CLASS REFINEMENT .............................................................................. 28 ANONYMOUS CLASSES ................................................................................................................. 30 4. SEQUENCES............................................................................................................... 33 SEQUENCE SYNTAX SUGAR ............................................................................................................ 33 ITERATING SEQUENCES................................................................................................................. 34 SEQUENCE AND ITS SUPERTYPES ...................................................................................................... 34 EMPTY SEQUENCES AND THE BOTTOM TYPE ........................................................................................ 37 SEQUENCE GOTCHAS FOR JAVA DEVELOPERS ........................................................................................ 38 5. UNIONS, ALGEBRAIC TYPES, AND TYPE INFERENCE .................................................... 41 NARROWING THE TYPE OF AN OBJECT REFERENCE .................................................................................. 41 MORE ABOUT UNION TYPES ........................................................................................................... 41 ENUMERATED SUBTYPES............................................................................................................... 42 VISITORS ................................................................................................................................. 43 TYPESAFE ENUMERATIONS ............................................................................................................ 45 TYPE INFERENCE ........................................................................................................................ 47 TYPE INFERENCE FOR SEQUENCE ENUMERATION EXPRESSIONS ................................................................... 48 6. GENERICS .................................................................................................................. 49 DEFINING GENERIC TYPES .............................................................................................................. 49 COVARIANCE AND CONTRAVARIANCE ................................................................................................ 50 GENERIC TYPE CONSTRAINTS .......................................................................................................... 53 7. ATTRIBUTES AND LOCALS, CONTROL STRUCTURES, AND IMPORTS ............................ 57 ATTRIBUTES AND LOCALS .............................................................................................................. 57
ADDING INLINE DOCUMENTATION
2|P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

VARIABLES............................................................................................................................... 58

................................................................................................................................. 58 CONTROL STRUCTURES................................................................................................................. 59 SEQUENCED PARAMETERS ............................................................................................................. 61 PACKAGES AND IMPORTS .............................................................................................................. 61 8. HIGHER ORDER FUNCTIONS....................................................................................... 63 FIRST CLASS AND HIGHER ORDER FUNCTIONS ....................................................................................... 63 REPRESENTING THE TYPE OF A FUNCTION ........................................................................................... 63 REPRESENTING THE TYPE OF A METHOD ............................................................................................. 64 DEFINING HIGHER ORDER FUNCTIONS ................................................................................................ 65 FUNCTION REFERENCES ................................................................................................................ 66 MORE ABOUT HIGHER-ORDER FUNCTIONS .......................................................................................... 67 9. NAMED ARGUMENTS AND THE BUILDER SYNTAX ...................................................... 72 NAMED ARGUMENTS .................................................................................................................. 72 DECLARATIVE OBJECT INSTANTIATION SYNTAX...................................................................................... 73 MORE ABOUT NAMED ARGUMENTS .................................................................................................. 74 DEFINING USER INTERFACES ........................................................................................................... 78 10. BASIC TYPES AND OPERATORS .................................................................................. 80 AN OVERVIEW OF THE LANGUAGE MODULE ......................................................................................... 80 EQUALITY AND IDENTITY ............................................................................................................... 81 OPERATOR POLYMORPHISM .......................................................................................................... 82 THE SLOTS INTERFACE .................................................................................................................. 83 NUMERIC TYPES......................................................................................................................... 84 NUMERIC LITERALS ..................................................................................................................... 84 NUMERIC WIDENING ................................................................................................................... 85 NUMERIC OPERATOR SEMANTICS..................................................................................................... 86 11. INITIALIZATION ......................................................................................................... 88 SELF REFERENCES AND OUTER INSTANCE REFERENCES ............................................................................. 88 MULTIPLE INHERITANCE AND LINEARIZATION ....................................................................................... 88 DEFINITE ASSIGNMENT AND DEFINITE INITIALIZATION ............................................................................. 89 CLASS BODIES ........................................................................................................................... 90 INITIALIZER SECTION ................................................................................................................... 91 DECLARATION SECTION ................................................................................................................ 93 CIRCULAR REFERENCES ................................................................................................................. 94 DEFINITE INITIALIZATION OF METHODS .............................................................................................. 94 DEFINITE RETURN ....................................................................................................................... 95 12. ANNOTATIONS AND INTERCEPTORS .......................................................................... 96 ANNOTATIONS .......................................................................................................................... 96 ANNOTATION ARGUMENTS ........................................................................................................... 96 ANNOTATION TYPES .................................................................................................................... 97 ANNOTATION CONSTRAINTS .......................................................................................................... 98 READING ANNOTATION VALUES AT RUNTIME ....................................................................................... 99 DEFINING ANNOTATIONS ............................................................................................................ 100 INTERCEPTORS ........................................................................................................................ 101
SETTERS
3|P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

CONCLUSION ..........................................................................................................................

102

4|P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

1. Basics
About Ceylon
Ceylon is a new programming language, designed to execute on the Java Virtual Machine, currently under development by my team here at Red Hat. We're fans of Java and of the Java ecosystem, of its practical orientation, of its culture of openness, of its developer community, of its roots in the world of business computing, and of its ongoing commitment to portability. However, we recognize that the language and class libraries, designed more than 15 years ago, are no longer the best foundation for a range of today's business computing problems. 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, to provide great support for meta-programming, thus making it easier to write frameworks, and to provide built-in modularity. Above all, Ceylon is designed to make it easy and fun to get things done in large teams. The Ceylon compiler is not yet finished, so you can't write code in Ceylon yet. However, we would like to get the community involved in development of the language and SDK, so this serious of articles is a sneak preview for interested folks. Let's start from the beginning.

Writing a simple program


Here's a classic example program.
5|P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

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

This method prints Hello, World! on the console. A toplevel method like this is just like a C function it belongs directly to the package that contains it, it's not a member of any specific type. You don't need a receiving object to invoke a toplevel method. Instead, you can just call it like this:
hello();

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. This method is declared using the void keyword, which means that the method declaration doesn't specify a return value. When the method is executed, it calls another toplevel method named writeLine(). This method displays its parameter on the console. Along with the void keyword, there's also a type named Void, which is the return type of any void method, and also the root of the Ceylon type system.
doc "The root type, supertype of both Object (definite values) and Nothing (the null value)." shared abstract class Void() of Object | Nothing {}

It might seem strange that a void method has a return type. The justification for this is that all methods in Ceylon are functions. (But not necessarily pure functions like hello(), a Ceylon function can have sideeffects.) At a very abstract level, every method accepts arguments and returns a result. The type Void, loosely speaking, is a way of representing the notion of an unknown value of an unknown type. You can assign any value, including null, to Void, but then there's no way to get it back out again, or even discover what type of value it was. So, in theory, a void method has a return value, we just have no way of finding out anything about that value. This stuff probably doesn't sound very useful right now, but it will turn out to be useful when we discuss higher order functions and Ceylon's typesafe metamodel.
6|P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Adding inline documentation


It's usually a good idea to add some kind of documentation to important methods like hello(). One way we could do this is by using a C-style comment, either like this:
/* The classic Hello World program */ void hello() { writeLine("Hello, World!"); }

Or like this:
//The classic Hello World program void hello() { writeLine("Hello, World!"); }

But it's much better to use the declarations.

doc

annotation for comments that describe

doc "The classic Hello World program" void hello() { writeLine("Hello, World!"); }

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.
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. Notice that when an annotation argument is a literal, it doesn't need to be enclosed in parentheses.
7|P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Note also that annotations like doc, by, see, and deprecated aren't keywords. They're just ordinary identifiers. The same is true for annotations which are part of the language definition: abstract, variable, shared, formal, actual, and friends. On the other hand, void is a keyword.

Strings and string interpolation


The Hello World program wildly popular as it is provides for a rather limited and monotonous user experience. A more typical program would produce output that sometimes varies between different runs. Let's ask our program to tell us a little more about itself.
doc "The Hello World program ... version 1.1!" void hello() { writeLine("Hello, this is Ceylon " process.languageVersion " running on Java " process.javaVersion "!"); }

Here we can see 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. A string template must begin and end in a string literal. The following is not legal syntax:
writeLine("Hello, this is Ceylon " process.languageVersion); //compile error!

A quick fix is to terminate the template with an empty string literal:


writeLine("Hello, this is Ceylon " process.languageVersion "");

Note that this isn't the only way to concatenate strings in Ceylon. Indeed, it's only useful when you're interpolating variables or expressions into an otherwise-constant string. The + operator you're probably used to is an alternative, and more flexible in many cases:
writeLine("Hello, this is Ceylon " + process.languageVersion + " running on Java " + process.javaVersion + "!");

However, don't make the mistake of thinking that these two approaches are always equivalent just because they result in the same output in this case.
8|P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

The + operator immediately evaluates its operand expressions and produces an immutable Stringobject. On the other hand, a string template is an expression of type Gettable<String>, which evaluates its interpolated expressions lazily. If you print the same string template object twice, you might see different output the second time around! In fact, a string template is a kind of closure an important concept we'll explore further in Part 2.

Dealing with objects that aren't there


An even more exciting program would receive input and produce output that depends upon the input it receives. Of course, this places somewhat more demanding requirements upon the user, but the extra work does sometimes pay off! Therefore, this improved version of the original Hello World program takes a name as input from the command line. We have to account for the case where nothing was specified at the command line, which gives us an opportunity to explore how null values are treated in Ceylon, which is quite different to what you're probably used to in Java or C#.
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 process object has an attribute named arguments, which holds a Sequence of the program's command line arguments. 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 optionaltype. And unlike other languages with typesafe null value handling, an optional type in Ceylon is not an algebraic datatype thatwraps the definite value. Instead, Ceylon uses an ad-hoc union
9|P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

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?.
doc "The type of null." shared abstract class Nothing() of nothing extends Void() {}

The value null is the unique instance of type Nothing, but it's not an instance of Object. So there's simply no way to assignnull to a local that isn't of optional type. The compiler won't let you.
doc "Represents a null reference." object nothing extends Nothing() {} doc "Hides the concrete type of nothing." shared Nothing null = nothing;

Nor will the Ceylon compiler let you do anything dangerous with a value of type T? that is, anything that could cause aNullPointerException in Java without first checking that the value is not null using if (exists ... ). More formally, the if (exists ... ) construct lets us extract a value of type X from a value of type X?, thereby allowing us to invoke the members of X upon the value. In fact, it's not even possible to use the equality operator == with an expression of optional type. You can't write if (x==null)like you can in Java. This helps avoid the undesirable behavior of == in Java where x==y evaluates to true if x and y both evaluate to null. It's possible to declare the local
String greeting; if (exists String name = process.arguments.first) { greeting = "Hello, " name "!"; } else { greeting = "Hello, World!"; } writeLine(greeting); name

inside the

if (exists ... )

condition:

This is the preferred style most of the time, since we can't actually use anything useful outside of the if (exists ... ) construct.
Source: http://relation.to/tag/Introduction+to+Ceylon

name

for

10 | P a g e

Operators for handling null values


While we're discussing null values, I should tell you about an even easier way to define greeting, using the ? operator. It's just a little extra syntax sugar to save some keystrokes.
shared 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:
shared String greeting = "Hello, " + nickName?name?"World";

The related ?. operator lets us call operations on optional types, always returning an optional type:
shared String shoutedGreeting = "HELLO, " + name?.uppercase?"WORLD";

Defaulted parameters
A method parameter may specify a default value.
void hello(String name="World") { writeLine("Hello, " name "!"); }

Then we don't need to specify an argument to the parameter when we call the method:
hello(); //Hello, World! hello("JBoss"); //Hello, JBoss!

Defaulted parameters must be declared after all required parameters in the parameter list of a method. Ceylon also supports sequenced parameters (varargs), declared using the syntax T.... We'll come back to them after we discuss sequences and for loops.

11 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

2. Classes and members


Creating your own classes
Ceylon is an object oriented language, so we usually write most of our code in classes. A class is a type that packages:

operations called methods, state held by attributes, logic to initialize the state when the object is first created, and, sometimes, other nested types. Types (interfaces, classes, and type parameters) have names that begin with uppercase letters. Members (methods and attributes) and locals have names that begin with lowercase letters. This is the rule you're used to from Java. Unlike Java, the Ceylon compiler enforces these rules. If you try to write class hello or String Name, you'll get a compilation error. Our first version of the
Hello

class has a single attribute and a single method:

doc "A personalized greeting" class Hello(String? name) {

doc "The greeting" shared String greeting; if (exists name) { greeting = "Hello, " name "!"; } else { greeting = "Hello, World!"; }

doc "Print the greeting" shared void say(OutputStream stream) { stream.writeLine(greeting); } }

To understand this code completely, we're going to need to first explore Ceylon's approach to program element accessibility the shared annotation

12 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

above, then meet the concept of an attribute, and finally discuss how object initialization works in Ceylon.

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 public, protected, 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 sharedare 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 (moduleprivate) 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
13 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

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!

Abstracting state using attributes


The attribute greeting is a simple attribute, the closest thing Ceylon has to a Java field. Its value is specified immediately after it is declared. Usually we can declare and specify the value of an attribute in a single line of code.
shared String greeting = "Hello, " name "!"; shared Natural months = years * 12;

The Ceylon compiler forces us to specify a value of any simple attribute or local before making use of the simple attribute or local in an expression. Ceylon will never automatically initialize an attribute to any kind of default value or let code observe the value of an uninitialized attribute. This code results in an error at compile time:
Natural count; shared void inc() { count++; } //compile error

An attribute is a bit different to a Java field. It's an abstraction of the notion of a value. Some attributes are simple value holders like the one we've just seen; others are more like a getter method, or, sometimes, like a getter and setter method pair. Like methods, attributes are polymorphican attribute definition may be refined (overridden) by a subclass. We could rewrite the attribute
shared String greeting { if (exists name) { return "Hello, " name "!"; } else { return "Hello, World!"; } } greeting

as a getter:

Notice that the syntax of a getter declaration looks a lot like a method declaration with no parameter list.
14 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Clients of a class never need to know whether the attribute they access holds state directly, or is a getter that derives its value from other attributes of the same object or other objects. In Ceylon, you don't need to go around declaring all your attributes private and wrapping them in getter and setter methods. Get out of that habit right now!

Understanding object initialization


In Ceylon, classes don't have constructors. Instead:

the parameters needed to instantiate the class the initializer parameters are declared directly after the name of the class, and code to initialize the new instance of the class the class initializer goes directly in the body of the class. Take a close look at the following code fragment:
String greeting; if (exists name) { greeting = "Hello, " name "!"; } else { greeting = "Hello, World!"; }

In Ceylon, this code could appear in the body of a class, where it would be declaring and specifying the value of an immutable attribute, or it could appear in the body of a method definition, where it would be declaring and specifying the value of an immutable local variable. That's not the case in Java, where initialization of fields looks very different to initialization of local variables! Thus the syntax of Ceylon is more regular than Java. Regularity makes a language easy to learn and easy to refactor. Now let's turn our attention to a different possible implementation of greeting:
class Hello(String? name) { shared String greeting { if (exists name) { return "Hello, " name "!"; } else { return "Hello, World!"; } }
15 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

... }

You might be wondering why we're allowed to use the parameter name inside the body of the getter of greeting. Doesn't the parameter go out of scope as soon as the initializer terminates? Well, that's true, but Ceylon is a language with a very strict block structure, and the scope of declarations is governed by that block structure. In this case, the scope of name is the whole body of the class, and the definition of greeting sits inside that scope, so greeting is permitted to access name. We've just met our first example of closure. We say that method and attribute definitions receive a closure of values defined in the class body to which they belong. That's just a fancy way of obfuscating the idea that greeting holds onto the value ofname, even after the initializer completes. In fact, one way to look at the whole notion of a class in Ceylon is to think of it as a function which returns a closure of its own local variables. This helps explain why the syntax of class declarations is so similar to the syntax of method declarations (a class declaration looks a lot like a method declaration where the return type and the name of the method are the same).

Instantiating classes and overloading their initializer parameters


Oops, I got so excited about attributes and closure and null value handling that I forgot to show you the code that uses Hello!
doc "Print a personalized greeting" void hello() { Hello(process.args.first).say(process.output); }

Our rewritten hello() method 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. No, we don't know why Java needs it. You'll have to ask James. 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
16 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

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. (If you're really impatient, skip forward to the discussion of generic type constraints.) 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
17 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

price to pay for a language with clearer semantics, without nasty corner cases, that is ultimately more powerful. Let's overload Hello, and its
doc "A command line greeting" class Hello(String? name = process.args.first) { ... say()

method, using defaulted parameters:

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(); }

18 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

3. Inheritance and introduction


Inheritance and refinement
In object-oriented programming, we often replace conditionals ( if, and especially switch) with subtyping. Indeed, according to some folks, this is what makes a program object-oriented. Let's try refactoring the Hello class from Part 2 into two classes, with two different implementations of greeting:
doc "A default greeting" class DefaultHello() { doc "The greeting" shared default String greeting = "Hello, World!";

doc "Print the greeting" shared void say(OutputStream stream) { stream.writeLine(greeting); } }

Notice that Ceylon forces us to declare attributes or methods that can be refined (overridden) by annotating them default. Subclasses specify their superclass using the extends keyword, followed by the name of the superclass, followed by a list of arguments to be sent to the superclass initializer parameters. It looks just like an expression that instantiates the superclass:
doc "A personalized greeting" class PersonalizedHello(String name) extends DefaultHello() {

doc "The personalized greeting" shared actual String greeting { return "Hello, " name "!"; } }

Ceylon also forces us to declare that an attribute or method refines (overrides) an attribute or method of a superclass by annotating it actual. All this annotating stuff costs a few extra keystrokes, but it helps the compiler detect
19 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

errors. We can't inadvertently refine a member or the superclass, or inadvertently fail to refine it. Notice that Ceylon goes out of its way to repudiate the idea of duck typing or structural typing. If it walks() like a Duck, then it should be a subtype of Duck and must explicitly refine the definition of walk() in Duck. We don't believe that the name of a method or attribute alone is sufficient to identify its semantics.

Abstract classes
There's one problem with what we've just seen. A personalized greeting is not really a kind of default greeting. This is a case for introducing an abstract superclass:
doc "A greeting" abstract class Hello() {

doc "The (abstract) greeting" shared formal String greeting;

doc "Print the greeting" shared void say(OutputStream stream) { stream.writeLine(greeting); } }

Ceylon requires us to annotate abstract classes abstract, just like Java. This annotation specifies that a class cannot be instantiated, and can define abstract members. Like Java, Ceylon also requires us to annotate abstract members that don't specify an implementation. However, in this case, the required annotation is formal. The reason for having two different annotations, as we'll see later, is that nested classes may be either abstract or formal, and abstract nested classes are slightly different to formal member classes a formal member class may be instantiated; an abstract class may not be. Note that an attribute that is never initialized is always a formal attribute Ceylon doesn't initialize attributes to zero or nullunless you explicitly tell it to! One way to define an implementation for an inherited abstract attribute is to simply assign a value to it in the subclass.
doc "A default greeting"

20 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

class DefaultHello() extends Hello() { greeting = "Hello, World!"; }

Of course, we can also define an implementation for an inherited abstract attribute by refining it.
doc "A personalized greeting" class PersonalizedHello(String name) extends Hello() {

doc "The personalized greeting" shared actual String greeting { return "Hello, " name "!"; } }

Note that there's no way to prevent a other code from extending a class in Ceylon. Since only members explicitly declared as supporting refinement using either formal or default can be refined, a subtype can never break the implementation of a supertype. Unless the supertype was explicitly designed to be extended, a subtype can add members, but never change the behavior of inherited members.

Interfaces and mixin inheritance


From time to time we come across a case where a class needs to inherit functionality from more than one supertype. Java's inheritance model doesn't support this, since an interface can never define a member with a concrete implementation. Interfaces in Ceylon are a little more flexible:

An interface may define concrete methods, attribute getters, and attribute setters. It may not define simple attributes or initialization logic. Notice that prohibiting simple attributes and initialization logic makes interfaces completely stateless. An interface can't hold references to other objects. Let's take advantage of mixin inheritance to define a reusable for Ceylon.
shared interface Writer {
21 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Writer

interface

shared formal Formatter formatter;

shared formal void write(String string);

shared void writeLine(String string) { write(string); write(process.newLine); }

shared void writeFormattedLine(String formatString, Object... args) { writeLine( formatter.format(formatString, args) ); } }

Note that we can't define a concrete value for the formatter attribute, since an interface may not define a simple attribute, and may not hold a reference to another object. Note also that the call to writeLine() from writeFormattedLine() resolves to the instance method of Writer, which hides the toplevel method of the same name. Now let's define a concrete implementation of this interface.
shared class ConsoleWriter() satisfies Writer { formatter = StringFormatter();

shared actual void write(String string) { writeLine(string); } }

The satisfies keyword is used to specify that an interface extends another interface or that a class implements an interface. Unlike an extends declaration, a satisfies declaration does not specify arguments, since interfaces do not have parameters or initialization logic. Furthermore, the satisfies declaration can specify more than one interface. Ceylon's approach to interfaces eliminates a common pattern in Java where a separate abstract class defines a default implementation of some of the members of an interface. In Ceylon, the default implementations can be
22 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

specified by the interface itself. Even better, it's possible to add a new member to an interface without breaking existing implementations of the interface.

Ambiguities in mixin inheritance


It's illegal for a type to inherit two members with the same name, unless the two members both (directly or indirectly) refine a common member of a common supertype, and the inheriting type itself also refines the member to eliminate any ambiguity. The following results in a compilation error:
interface Party { shared formal String legalName; shared default String name { return legalName; } } interface User { shared formal String userId; shared default String name { return userId; } } class Customer(String name, String email) satisfies User & Party { legalName = name; userId = email; shared actual String name = name; } //error: refines two different members

To fix this code, we'll factor out a formal declaration of the attribute common supertype. The following is legal:
interface Named { shared formal String name; } interface Party satisfies Named { shared formal String legalName; shared actual default String name { return legalName; } }

name

to a

23 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

interface User satisfies Named { shared formal String userId; shared actual default String name { return userId; } } class Customer(String name, String email) satisfies User & Party { legalName = name; userId = email; shared actual String name = name; }

Oh, of course, the following is illegal:


interface Named { shared formal String name; } interface Party satisfies Named { shared formal String legalName; shared actual String name { return legalName; } } interface User satisfies Named { shared formal String userId; shared actual String name { return userId; } } class Customer(String name, String email) satisfies User & Party { legalName = name; userId = email; } //error: inherits multiple definitions of name

To fix this code, name must be declared explicitly refined in Customer.

default

in both

User

and

Party

and

24 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Introduction
Sometimes, especially when we're working with code from modules we don't have control over, we would like to mix an interface into a type that has already been defined in another module. For example, we might like to introduce the Ceylon collections module type List into the language module type Sequence, so that all Sequences support all operations of List. But the language module shouldn't have a dependency to the collections module, so we can't specify that interface Sequence satisfies List in the declaration of Sequence in the language module. Instead, we can introduce the type Sequence in the code which uses the collections and language modules. The collections module already defines an interface called SequenceList for this purpose. Well, it doesn't yet, since we have not yet either implemented introductions or written the collections module, but it will soon!
doc "Decorator that introduces List to Sequence." see (List,Sequence) shared interface SequenceList<Element> adapts Sequence<Element> satisfies List<Element> {

shared actual default List<Element> sortedElements() { //define the operation of List in //terms of operations on Sequence return asList(sortSequence(this)); } ... }

The adapts clause makes SequenceList a special kind of interface called an adapter (in the terminology used by this book). According to the language spec: The interface may not:

declare or inherit a member that refines a member of any adapted type, or declare or inherit a formal or non-default actual member unless the member is inherited from an adapted type.

25 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

The purpose of an adapter is to add a new supertype, called an introduced type, to an existing type, called the adapted type. The adapter doesn't change the original definition of the adapted type, and it doesn't affect the internal workings of an instance of the adapted type in any way. All it does is fill in the definitions of the missing operations. Here, the SequenceListinterface provides concrete implementations of all methods of List that are not already implemented by Sequence. Now, to introduce List to is import the adapter:
Sequence

in a certain compilation unit, all we need to do

import ceylon.collection { List, SequenceList } ... //define a Sequence Sequence<String> names = { "Gavin", "Emmanuel", "Andrew", "Ales" }; //call an operation of List on Sequence List<String> sortedNames = names.sortedElements();

Note that the introduction is not visible outside the lexical scope of the import statement (the compilation unit). But within the compilation unit containing the import statement, every instance of of the adapted type Sequence now has all the attributes and methods of the introduced type and is assignable to the introduced type. Again, according to the spec:

List,

If, in a certain compilation unit, multiple introductions of a certain adapted type declare or inherit a member that refines a common member of a common supertype then either:

there must be a unique member from the set of members, called the most refined member, that refines all the other members, or the adapted type must declare or inherit a member that refines all the members.

At runtime, an operation (method invocation, member class instantiation, or attribute evaluation) upon any type that is a subtype of all the adapted types is dispatched according to the following rule:

If the runtime type of the instance of the adapted type declares or inherits a member defining the operation, the operation is dispatched to the runtime type of the instance.
26 | P a g e

Source: http://relation.to/tag/Introduction+to+Ceylon

Otherwise, the operation is dispatched to the introduction that has the most-refined member defining the operation.

Introduction compared to extension methods and implicit type conversions


Introduction is Ceylon's way of extending a type after it's been defined. It's interesting to compare introduction to the following features of other languages:

extension methods, and


user-defined implicit type conversions. Introduction is really just a much more powerful cousin of extension methods. From our point of view, an extension method introduces a member to a type, without actually introducing a new supertype. Indeed, a Ceylon adapter with no satisfiesclause is actually a package of extension methods!
shared interface StringSequenceExtensions adapts Sequence<String> {

shared String concatenated { variable String concat = ""; for (String s in this) { concat+=s; } return concat; }

shared String join(String separator=", ") { ... } }

On the other hand, introductions are less powerful than implicit type conversions. This is by design! In this case, less powerful means safer, more disciplined. The power of implicit type conversions comes partly from their ability to work around some of the designed-in limitations of the type system. But these limitations have a purpose! I'm especially thinking of the prohibitions against:

inheriting the same generic type twice, with different type arguments (in most languages), and
27 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

overloading (in Ceylon). Implicit type conversions are an end-run around these restrictions, reintroducing the ambiguities that these restrictions exist to solve. Furthermore, it's extremely difficult to imagine a language with implicit type conversions that preserve the following important properties of the type system:

transitivity of the assignability relationship, covariance of generic types, the semantics of the identity
==

operator, and

the ability to infer generic type arguments of an invocation or instantiation. Finally, implicit type conversions work by having the compiler introduce hidden invocations of arbitrary user-written procedural code, code that could potentially have side-effects or make use of temporal state. Thus, the observable behavior of the program can depend upon precisely where and how the compiler introduces these magic calls. Introductions are a kind of elegant compromise: more powerful than plain extension methods, safer than implicit type conversions. We think the beauty of this model is a major advantage of Ceylon over similar languages.

Type aliases
It's often useful to provide a shorter or more semantic name to an existing class or interface type, especially if the class or interface is a parameterized type. For this, we use a type alias, for example:
interface People = Set<Person>;

A class alias must declare its formal parameters:


shared class People(Person... people) = ArrayList<Person>;

Member classes and member class refinement


You're probably used to the idea of an inner class in Java a class declaration nested inside another class or method. Since Ceylon is a language with a recursive block structure, the idea of a nested class is more than natural. But in Ceylon, a non-abstract nested class is actually considered a member of the containing type. For example, BufferedReader defines the member class Buffer:
28 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

class BufferedReader(Reader reader) satisfies Reader { shared default class Buffer() satisfies List<Character> { ... } ... }

The member class

Buffer

is annotated

shared,

so we can instantiate it like this:

BufferedReader br = BufferedReader(reader); BufferedReader.Buffer b = br.Buffer();

Note that a nested type name must be qualified by the containing type name when used outside of the containing type. The member class Buffer is also annotated subtype of BufferedReader:
shared class BufferedFileReader(File file) extends BufferedReader(FileReader(file)) { shared actual class Buffer() extends super.Buffer() { ... } } default,

so we can refine it in a

That's right: Ceylon lets us override a member class defined by a supertype! Note that
BufferedFileReader.Buffer

is a subclass of

BufferedReader.Buffer.

Now the instantiation br.Buffer() above is a polymorphic operation! It might return an instance ofBufferedFileReader.Buffer or an instance of BufferedReader.Buffer, depending upon whether br refers to a plainBufferedReader or a BufferedFileReader. This is more than a cute trick. Polymorphic instantiation lets us eliminate thefactory method pattern from our code. It's even possible to define a formal member class of an A formal member class can declare formal members.
abstract class BufferedReader(Reader reader) satisfies Reader { shared formal class Buffer() { shared formal Byte read(); } ... }
29 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

abstract

class.

In this case, a concrete subclass of the the formal member class.


shared class BufferedFileReader(File file)

abstract

class must refine

extends BufferedReader(FileReader(file)) { shared actual class Buffer() extends super.Buffer() { shared actual Byte read() { ... } } }

Notice the difference between an abstract class and a formal member class. An abstract nested class may not be instantiated, and need not be refined by concrete subclasses of the containing class. A formal member class may be instantiated, and must be refined by every subclass of the containing class. It's an interesting exercise to compare Ceylon's member class refinement with the functionality of Java dependency injection frameworks. Both mechanisms provide a means of abstracting the instantiation operation of a type. You can think of the subclass that refines a member type as filling the same role as a dependency configuration in a dependency injection framework.

Anonymous classes
If a class has no parameters, it's often possible to use a shortcut declaration which defines a named instance of the class, without providing any actual name for the class itself. This is usually most useful when we're extending an abstract class or implementing an interface.
doc "A default greeting" object defaultHello extends Hello() { greeting = "Hello, World!"; } shared object consoleWriter satisfies Writer { formatter = StringFormatter();

shared actual void write(String string) { writeLine(string); } }

30 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

The downside to an object declaration is that we can't write code that refers to the concrete type of defaultHello orconsoleWriter, only to the named instances. You might be tempted to think of that's not quite right:

object

declarations as defining singletons, but

A toplevel

object

declaration does define a singleton.

An object declaration nested inside a class defines an object per instance of the containing class. An object declaration nested inside a method, getter, or setter results in an new object each time the method, getter, or setter is executed. Let's see how this can be useful:
interface Subscription { shared formal void cancel(); } shared Subscription register(Subscriber s) { subscribers.append(s); object subscription satisfies Subscription { shared actual void cancel() { subscribers.remove(s); } } return subscription; }

Notice how this code example makes clever use of the fact that the nested object declaration receives a closure of the locals defined in the containing method declaration! A different way to think about the difference between object and class is to think of a class as a parametrized object. (Of course, there's one big difference: a class declaration defines a named type that we can refer to in other parts of the program.) We'll see later that Ceylon also lets us think of a method as a parametrized attribute. An
object

declaration can refine an attribute declared

formal

or

default.

shared abstract class App() { shared formal OutputStream stream; ... } class ConsoleApp() extends App() {
31 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

shared actual object stream satisfies OutputStream { ... } ... }

However, an

object

may not itself be declared

formal

or

default.

32 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

4. Sequences
Some kind of array or list construct is a universal feature of all programming languages. The Ceylon language module defines support for sequence types. A sequence type is usually written X[] for some element type X. But this is really just an abbreviation for the union type Empty|Sequence<X>. The interface Sequence represents a sequence with at least one element. The type Empty represents an empty sequence with no elements. Some operations of the type Sequence aren't defined by Empty, so you can't call them if all you have is X[]. Therefore, we need the if (nonempty ... ) construct to gain access to these operations.
void printBounds(String[] strings) { if (nonempty strings) { //strings is a Sequence<String> writeLine(strings.first + ".." + strings.last); } else { writeLine("Empty"); } }

Note how this is just a continuation of the pattern established for null value handling.

Sequence syntax sugar


There's lots more syntactic sugar for sequences. We can use a bunch of familiar Java-like syntax:
String[] operators = { "+", "-", "*", "/" }; String? plus = operators[0]; String[] multiplicative = operators[2..3];

Oh, and the expression

{}

returns a value of type

Empty.

However, unlike Java, all these syntactic constructs are pure abbreviations. The code above is exactly equivalent to the following de-sugared code:
Empty|Sequence<String> operators = Array("+", "-", "*", "/"); Nothing|String plus = operators.value(0); Empty|Sequence<String> multiplicative = operators.range(2,3);

33 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Range

is also a subtype of

Sequence.

The following:

Character[] uppercaseLetters = 'A'..'Z'; Natural[] countDown = 10..0;

Is just sugar for:


Empty|Sequence<Character> uppercaseLetters = Range('A','Z'); Empty|Sequence<Natural> countDown = Range(10,0);

In fact, this is just a sneak preview of the fact that almost all operators in Ceylon are just sugar for method calls upon a type. We'll come back to this later, when we talk about operator polymorphism.

Iterating sequences
The Sequence interface extends a for loop:
for (String op in operators) { writeLine(op); } Iterable,

so we can iterate a

Sequence

using

Ceylon doesn't need C-style operator ...


variable Natural fac:=1; for (Natural n in 1..100) { fac*=n;

for

loops. Instead, combine

for

with the range

writeLine("Factorial " n "! = " fac ""); }

If, for any reason, we need to use the index of each element of a sequence we can use a special variation of the for loop that is designed for iterating instances of Entries:
for (Natural i -> String op in entries(operators)) { writeLine($i + ": " + op); }

The entries() function returns an instance of the indexed elements of the sequence.

Entries<Natural,String>

containing

Sequence and its supertypes


34 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

It's probably a good time to see some more advanced Ceylon code. What better place to find some than in the language module itself? Here's how the language module defines the type
shared interface Sequence<out Element> satisfies Correspondence<Natural, Element> & Iterable<Element> & Sized { Sequence:

doc "The index of the last element of the sequence." shared formal Natural lastIndex;

doc "The first element of the sequence." shared actual formal Element first;

doc "The rest of the sequence, without the first element." shared formal Element[] rest; shared actual Boolean empty { return false; }

shared actual default Natural size { return lastIndex+1; }

doc "The last element of the sequence." shared default Element last { if (exists Element x = value(lastIndex)) { return x; } else { //actually never occurs if //the subtype is well-behaved return first; } } shared actual default Iterator<Element> iterator() { class SequenceIterator(Natural from) satisfies Iterator<Element> { shared actual Element? head { return value(from);
35 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

} shared actual Iterator<Element> tail { return SequenceIterator(from+1); } } return SequenceIterator(0); } }

The most interesting operations are inherited from Correspondence, Iterable and Sized:
shared interface Correspondence<in Key, out Value> given Key satisfies Equality {

doc "Return the value defined for the given key." shared formal Value? value(Key key); } shared interface Iterable<out Element> satisfies Container {

doc "An iterator of values belonging to the container." shared formal Iterator<Element> iterator();

shared actual default Boolean empty { return !(first exists); }

doc "The first object." shared default Element? first { return iterator().head; } } shared interface Sized satisfies Container {

doc "The number of values or entries belonging to the container." shared formal Natural size;
36 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

shared actual default Boolean empty { return size==0; } } shared interface Container {

shared formal Boolean empty; }

Empty sequences and the Bottom type


Now let's see the definition of
Empty:

object emptyIterator satisfies Iterator<Bottom> {

shared actual Nothing head { return null; } shared actual Iterator<Bottom> tail { return this; } } shared interface Empty satisfies Correspondence<Natural, Bottom> & Iterable<Bottom> & Sized {

shared actual Natural size { return 0; } shared actual Boolean empty { return true; } shared actual Iterator<Bottom> iterator() { return emptyIterator; } shared actual Nothing value(Natural key) { return null; } shared actual Nothing first {
37 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

return null; } }

The special type


Bottom

represents:

the empty set, or equivalently the intersection of all types. Since the empty set is a subset of all other sets, Bottom is assignable to all other types. Why is this useful here? Well,Correspondence<Natural,Element> and Iterable<Element> are both covariant in the type parameter Element. So Empty is assignable to Correspondence<Natural,T> and Iterable<T> for any type T. That's why Empty doesn't need a type parameter. The following code is well-typed:
void printAll(String[] strings) { variable Iterator<String> i := strings.iterator(); while (exists String s = i.head) { writeLine(s); i := i.tail; } }

Since both Empty and Sequence<String> are subtypes of type String[] is also a subtype ofIterable<String>.

Iterable<String>,

the union

Another cool thing to notice here is the return type of the first and value() operations of Empty. You might have been expecting to see Bottom? here, since they override supertype members of type T?. But as we saw in Part 1, Bottom? is just an abbreviation for Nothing|Bottom. And Bottom is the empty set, so the union Bottom|T of Bottom with any other type T is just Titself. The Ceylon compiler is able to do all this reasoning automatically. So when it sees an Iterable<Bottom>, it knows that the operation first is of type Nothing, i.e. it is the value null. Cool, huh?

Sequence gotchas for Java developers


Superficially, a sequence type looks a lot like a Java array, but really it's very, very different! First, of course, a sequence typeSequence<String> is an immutable
38 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

interface, it's not a mutable concrete type like an array. We can't set the value of an element:
String[] operators = .... ; operators[0] := "**"; //compile error

Furthermore, the index operation operators[i] returns an optional type String?, which results in quite different code idioms. To begin with, we don't iterate sequences by index like in C or Java. The following code does not compile:
for (Natural i in 0..operators.size-1) { String op = operators[i]; //compile error ... }

Here,

operators[i]

is a

String?,

which is not directly assignable to

String. for

Instead, if we need access to the index, we use the special form of above.
for (Natural i -> String op in entries(operators)) { ... }

shown

Likewise, we don't usually do an upfront check of an index against the sequence length:
if (i>operators.size-1) { throw IndexOutOfBoundException(); } else { return operators[i]; //compile error }

Instead, we do the check after accessing the sequence element:


if (exists String op = operators[i]) { return op; } else { throw IndexOutOfBoundException(); }

We especially don't ever need to write the following:


39 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

if (i>operators.size-1) { return ""; } else { return operators[i]; //compile error }

This is much cleaner:


return operators[i] ? "";

All this may take a little getting used to. But what's nice is that all the exact same idioms also apply to other kinds ofCorrespondence, including Entries and Maps.

40 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

5. Unions, algebraic types, and type inference


Narrowing the type of an object reference
In any language with subtyping there is the hopefully occasional need to perform narrowing conversions. In most statically-typed languages, this is a two-part process. For example, in Java, we first test the type of the object using the instanceofoperator, and then attempt to downcast it using a C-style typecast. This is quite curious, since there are virtually no good uses for instanceof that don't involve an immediate cast to the tested type, and typecasts without type tests are dangerously non-typesafe. As you can imagine, Ceylon, with its emphasis upon static typing, does things differently. Ceylon doesn't have C-style typecasts. Instead, we must test and narrow the type of an object reference in one step, using the special if (is ... )construct. This construct is very, very similar to if (exists ... ) and if (nonempty ... ), which we met earlier.
Object obj = ... ; if (is Hello obj) { obj.say(); }

The

switch

statement can be used in a similar way:

Object obj = ... ; switch(obj) case (is Hello) { obj.say(); } case (is Person) { stream.writeLine(obj.firstName); } else { stream.writeLine("Some miscellaneous thing"); }

These constructs protect us from inadvertantly writing code that would cause a ClassCastException in Java, just like if (exists ... ) protects us from writing code that would cause a NullPointerException.

More about union types


41 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

We've seen a few examples of how ad-hoc union types are used in Ceylon. Let's just revisit the notion to make sure we completely understand it. When I declare the type of something using a union type X|Y, I'm saying that only expressions of type X and expressions of type Y are assignable to it. The type X|Y is a supertype of both X and Y. The following code is well-typed:
void print(String|Natural|Integer val) { ... } print("hello"); print(69); print(-1);

But what operations does a type like String|Natural|Integer have? What are its supertypes? Well, the answer is pretty intuitive: T is a supertype of X|Y if and only if it is a supertype of both X and Y. The Ceylon compiler determines this automatically. So the following code is also well-typed:
Natural|Integer i = ... ; Number num = i; String|Natural|Integer val = i; Object obj = val;

However,

num

is not assignable to

val,

since

Number

is not a supertype of

String.

Of course, it's very common to narrow an expression of union type using a switch statement. Usually, the Ceylon compiler forces us to write an else clause in a switch, to remind us that there might be additional cases which we have not handled. But if we exhaust all cases of a union type, the compiler will let us leave off the else clause.
void print(String|Natural|Integer val) { switch (val) case (is String) { writeLine(val); } case (is Natural) { writeLine("Natural: " + val); } case (is Integer) { writeLine("Integer: " + val); } }

Enumerated subtypes
Sometimes it's useful to be able to do the same kind of thing with the subtypes of an ordinary type. First, we need to explicitly enumerate the subtypes of the type using the of clause:
abstract class Hello() of DefaultHello | PersonalizedHello {
42 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

... }

(This makes Hello into Ceylon's version of what the functional programming community calls an algebraic data type.) Now the compiler won't let us declare additional subclasses of Hello, and so the union type DefaultHello|PersonalizedHellois exactly the same type as Hello. Therefore, we can write switch statements without an else clause:
Hello hello = ... ; switch (hello) case (is DefaultHello) { writeLine("What's your name?"); } case (is PersonalizedHello) { writeLine("Nice to hear from you again!"); }

Now, it's usually considered bad practice to write long switch statements that handle all subtypes of a type. It makes the code non-extensible. Adding a new subclass to Hello means breaking all the switch statements that exhaust its subtypes. In object-oriented code, we usually try to refactor constructs like this to use an abstract method of the superclass that is overridden as appropriate by subclasses. However, there are a class of problems where this kind of refactoring isn't appropriate. In most object-oriented languages, these problems are usually solved using the visitor pattern.

Visitors
Let's consider the following tree visitor implementation:
abstract class Node() { shared formal void accept(Visitor v); } class Leaf(Object val) extends Node() { shared Object value = val; shared actual void accept(Visitor v) { v.visitLeaf(this); } } class Branch(Node left, Node right) extends Node() { shared Node leftChild = left;
43 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

shared Node rightChild = right; shared actual void accept(Visitor v) { v.visitBranch(this); } } interface Visitor { shared formal void visitLeaf(Leaf l); shared formal void visitBranch(Branch b); }

We can create a method which prints out the tree by implementing the Visitor interface:
void print(Node node) { object printVisitor satisfies Visitor { shared actual void visitLeaf(Leaf l) { writeLine("Found a leaf: " l.value "!"); } shared actual void visitBranch(Branch b) { b.leftChild.accept(this); b.rightChild.accept(this); } } node.accept(printVisitor); }

Notice that the code of printVisitor looks just like a switch statement. It must explicitly enumerate all subtypes of Node. Itbreaks if we add a new subtype of Node to the Visitor interface. This is correct, and is the desired behavior. By break, I mean that the compiler lets us know that we have to update our code to handle the new subtype. In Ceylon, we can achieve the same effect, with less verbosity, by enumerating the subtypes of Node in its definition, and using a switch:
abstract class Node() of Leaf | Branch {} class Leaf(Object val) extends Node() { shared Object value = val; } class Branch(Node left, Node right) extends Node() { shared Node leftChild = left; shared Node rightChild = right; }

44 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Our print() method is now much simpler, but still has the desired behavior of breaking when a new subtype of Node is added.
void print(Node node) { switch (node) case (is Leaf) { writeLine("Found a leaf: " node.value "!"); } case (is Branch) { print(node.leftChild); print(node.rightChild); } }

Typesafe enumerations
Ceylon doesn't have anything exactly like Java's emulate the effect using the of clause.
shared class Suit(String name) of hearts | diamonds | clubs | spades extends Case(name) {} enum

declaration. But we can

shared object hearts extends Suit("hearts") {} shared object diamonds extends Suit("diamonds") {} shared object clubs extends Suit("clubs") {} shared object spades extends Suit("spades") {}

We're allowed to use the names of extend the language module class Now we can exhaust all cases of
void print(Suit suit) { switch (suit)

object Case.

declarations in the
switch:

of

clause if they

Suit

in a

case (hearts) { writeLine("Heartzes"); } case (diamonds) { writeLine("Diamondzes"); } case (clubs) { writeLine("Clidubs"); } case (spades) { writeLine("Spidades"); } }

(Note that these

cases

are ordinary value

cases, enum,

not

case (is...)

type cases.)

Yes, this is a bit more verbose than a Java flexible.


Source: http://relation.to/tag/Introduction+to+Ceylon

but it's also slightly more


45 | P a g e

For a more practical example, let's see the definition of language module:
shared abstract class Boolean(String name) of true | false extends Case(name) {} shared object false extends Boolean("false") {} shared object true extends Boolean("true") {}

Boolean

from the

And here's how

Comparable

is defined. First, the typesafe enumeration

Comparison:

doc "The result of a comparison between two Comparable objects." shared abstract class Comparison(String name) of larger | smaller | equal extends Case(name) {} doc "The receiving object is exactly equal to the given object." shared object equal extends Comparison("equal") {} doc "The receiving object is smaller than the given object." shared object smaller extends Comparison("smaller") {} doc "The receiving object is larger than the given object." shared object larger extends Comparison("larger") {}

Now, the

Comparable

interface itself:

shared interface Comparable<in Other> satisfies Equality given Other satisfies Comparable<Other> {

doc "The <=> operator." shared formal Comparison compare(Other other);

doc "The > operator." shared Boolean largerThan(Other other) { return compare(other)==larger; }

doc "The < operator." shared Boolean smallerThan(Other other) { return compare(other)==smaller; }
46 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

doc "The >= operator." shared Boolean asLargeAs(Other other) { return compare(other)!=smaller; }

doc "The <= operator." shared Boolean asSmallAs(Other other) { return compare(other)!=larger; } }

Type inference
So far, we've always been explicitly specifying the type of every declaration. I think this generally makes code, especially example code, much easier to read and understand. However, Ceylon does have the ability to infer the type of a locals or the return type of a local method. Just place the keywordlocal in place of the type declaration.
local hello = DefaultHello(); local operators = { "+", "-", "*", "/" }; local add(Natural x, Natural y) { return x+y; }

There are some restrictions applying to this feature. You can't use

local:

for declarations annotated for declarations annotated for methods with multiple to declare a parameter.

shared, formal,

when the value is specified later in the block of statements,


return

statements, or

These restrictions mean that Ceylon's type inference rules are quite simple. Type inference is purely right-to-left and top-to-bottom. The type of any expression is already known without needing to look to any types declared to the left of the =specifier, or further down the block of statements.

The inferred type of a local declared assigned to it using = or :=.

local

is just the type of the expression

47 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

The inferred type of a method declared expression.

local

is just the type of the

returned

Type inference for sequence enumeration expressions


What about sequence enumeration expressions like this:
local sequence = { DefaultHello(), "Hello", 12.0 };

What type is inferred for sequence? You might answer: Sequence<X> where X is the common superclass or super-interface of all the element types. But that can't be right, since there might be more than one common supertype. The answer is that the inferred type is Sequence<X> where X is the union of all the element expression types. In this case, the type is Sequence<DefaultHello|String|Float>. Now, this works out nicely, because Sequence<T> is covariant in T. So the following code is well typed:
local sequence = { DefaultHello(), "Hello", 12.0 }; //type Sequence<DefaultHello|String|Float> Object[] objects = sequence; //type Empty|Sequence<Object>

As is the following code:


local nums = { 12.0, 1, -3 }; //type Sequence<Float|Natural|Integer> Number[] numbers = nums; //type Empty|Sequence<Number>

What about sequences that contain of null from Part 1 was Nothing?

null?

Well, do you remember the type

local sequence = { null, "Hello", "World" }; //type Sequence<Nothing|String> String?[] strings = sequence; //type Empty|Sequence<Nothing|String> String? s = sequence[0]; //type Nothing|Nothing|String which is just Nothing|String

It's interesting just how useful union types turn out to be. Even if you only very rarely explicitly write code with any explicit union type declaration (and that's probably a good idea), they are still there, under the covers, helping the compiler solve some hairy, otherwise-ambiguous, typing problems.

48 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

6. Generics
Defining generic types
We've seen plenty of parameterized types in this series of articles, but now let's explore a few more details. Programming with generic types is one of the most difficult parts of Java. That's still true, to some extent, in Ceylon. But because the Ceylon language and SDK were designed for generics from the ground up, Ceylon is able to alleviate the most painful aspects of Java's bolted-on-later model. Just like in Java, only types and methods may declare type parameters. Also just like in Java, type parameters are listed before ordinary parameters, enclosed in angle brackets.
shared interface Iterator<out Element> { ... } class Array<Element>(Element... elements) satisfies Sequence<Element> { ... } shared Entries<Natural,Value> entries<Value>(Value... sequence) { ... }

As you can see, the convention in Ceylon is to use meaningful names for type parameters. Unlike Java, we always do need to specify type arguments in a type declaration (there are no raw types in Ceylon). The following will not compile:
Iterator it = ...; //error: missing type argument to parameter Element of Iterator

We always have to specify a type argument in a type declaration:


Iterator<String> it = ...;

On the other hand, we shouldn't need to explicitly specify type arguments in most method invocations or class instantiations. In principle it's very often possible to infer the type arguments from the ordinary arguments. The following code should be possible, just like it is in Java:
Array<String> strings = Array("Hello", "World"); Entries<Natural,String> entries = entries(strings);

But we haven't yet figured out what exactly the type inference algorithm will be (probably something involving union types!) and so the Ceylon compiler currently requires that all type arguments be explicitly specified like this:
49 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Array<String> strings = Array<String>("Hello", "World"); Entries<Natural,String> entries = entries<Natural,String>(strings);

On the other hand, the following code does already compile:


local strings = Array<String>("Hello", "World"); local entries = entries<Natural,String>(strings);

The root cause of very many problems when working with generic types in Java is type erasure. Generic type parameters and arguments are discarded by the compiler, and simply aren't available at runtime. So the following, perfectly sensible, code fragments just wouldn't compile in Java:
if (is List<Person> list) { ... } if (is Element obj) { ... }

(Where

Element

is a generic type parameter.)

A major goal of Ceylon's type system is support for reified generics. Like Java, the Ceylon compiler performs erasure, discarding type parameters from the schema of the generic type. But unlike Java, type arguments are supposed to be reified (available at runtime). Of course, generic type arguments won't be checked for typesafety by the underlying virtual machine at runtime, but type arguments are at least available at runtime to code that wants to make use of them explicitly. So the code fragments above are supposed to compile and function as expected. You will even be able to use reflection to discover the type arguments of an instance of a generic type. The bad news is we haven't implemented this yet ;-) Finally, Ceylon eliminates one of the bits of Java generics that's really hard to get your head around: wildcard types. Wildcard types were Java's solution to the problem of covariance in a generic type system. Let's first explore the idea of covariance, and then see how covariance in Ceylon works.

Covariance and contravariance


It all starts with the intuitive expectation that a collection of Geeks is a collection of Persons. That's a reasonable intuition, but especially in non-functional languages, where collections can be mutable, it turns out to be incorrect. Consider the following possible definition of Collection:
shared interface Collection<Element> { shared formal Iterator<Element> iterator(); shared formal void add(Element x);
50 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

And let's suppose that

Geek

is a subtype of

Person.

Reasonable.

The intuitive expectation is that the following code should work:


Collection<Geek> geeks = ... ; Collection<Person> people = geeks; for (Person person in people) { ... }

//compiler error

This code is, frankly, perfectly reasonable taken at face value. Yet in both Java and Ceylon, this code results in a compiler error at the second line, where the Collection<Geek> is assigned to a Collection<Person>. Why? Well, because if we let the assignment through, the following code would also compile:
Collection<Geek> geeks = ... ; Collection<Person> people = geeks; people.add( Person("Fonzie") );

//compiler error

We can't let that code by Fonzie isn't a

Geek!

Using big words, we say that Collection is nonvariant in Element. Or, when we're not trying to impress people with opaque terminology, we say that Collection both produces via the iterator() method and consumes via the add() method the type Element. Here's where Java goes off and dives down a rabbit hole, successfully using wildcards to squeeze a covariant or contravariant type out of a nonvariant type, but also succeeding in thoroughly confusing everybody. We're not going to follow Java down the hole. Instead, we're going to refactor pure consumer interface:
Collection

into a pure producer interface and a

shared interface Producer<out Output> { shared formal Iterator<Output> iterator(); } shared interface Consumer<in Input> { shared formal void add(Input x); }

Notice that we've annotated the type parameters of these interfaces.

The out annotation specifies that Producer is covariant in Output; that it produces instances of Output, but never consumes instances of Output.
51 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

The in annotation specifies that Consumer is contravariant in Input; that it consumes instances of Input, but never produces instances of Input. The Ceylon compiler validates the schema of the type declaration to ensure that the variance annotations are satisfied. If you try to declare an add() method on Producer, a compilation error results. If you try to declare an iterate() method onConsumer, you get a similar compilation error. Now, let's see what that buys us:

Since Producer is covariant in its type parameter Output, and since Geek is a subtype of Person, Ceylon lets you assign Producer<Geek> to Producer<Person>. Furthermore, since Consumer is contravariant in its type parameter since Geek is a subtype of Person, Ceylon lets you assign Consumer<Person> to Consumer<Geek>. We can define our
Collection Input,

and

interface as a mixin of

Producer

with

Consumer.

shared interface Collection<Element> satisfies Producer<Element> & Consumer<Element> {}

Notice that Collection remains nonvariant in Element. If we tried to add a variance annotation to Element in Collection, a compile time error would result. Now, the following code finally compiles:
Collection<Geek> geeks = ... ; Producer<Person> people = geeks; for (Person person in people) { ... }

Which matches our original intuition. The following code also compiles:
Collection<Person> people = ... ; Consumer<Geek> geekConsumer = people; geekConsumer.add( Geek("James") );

Which is also intuitively correct James is most certainly a

Person!

There's two additional things that follow from the definition of covariance and contravariance:

Producer<Void>

is a supertype of

Producer<T>

for any type T, and


52 | P a g e

Source: http://relation.to/tag/Introduction+to+Ceylon

Consumer<Bottom>

is a supertype of

Consumer<T>

for any type T.

These invariants can be very helpful if you need to abstract over all Producers or all Consumers. (Note, however, that if Producerdeclared upper bound type constraints on Output, then Producer<Void> would not be a legal type.) You're unlikely to spend much time writing your own collection classes, since the Ceylon SDK has a powerful collections framework built in. But you'll still appreciate Ceylon's approach to covariance as a user of the built-in collection types. The collections framework defines two interfaces for each basic kind of collection. For example, there's an interface List<Element>which represents a read-only view of a list, and is covariant in Element, and OpenList<Element>, which represents a mutable list, and is nonvariant in Element.

Generic type constraints


Very commonly, when we write a parameterized type, we want to be able to invoke methods or evaluate attributes upon instances of the type parameter. For example, if we were writing a parameterized type Set<Element>, we would need to be able to compare instances of Element using == to see if a certain instance of Element is contained in the Set. Since == is only defined for expressions of type Equality, we need some way to assert that Element is a subtype of Equality. This is an example of a type constraint in fact, it's an example of the most common kind of type constraint, an upper bound.
shared class Set<out Element>(Element... elements) given Element satisfies Equality { ... shared Boolean contains(Object obj) { if (is Element obj) { return obj in bucket(obj.hash); } else { return false; } } }

A type argument to

Element

must be a subtype of

Equality.

Set<String> set = Set("C", "Java", "Ceylon"); //ok Set<String?> set = Set("C", "Java", "Ceylon", null); //compile error

53 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

In Ceylon, a generic type parameter is considered a proper type, so a type constraint looks a lot like a class or interface declaration. This is another way in which Ceylon is more regular than some other C-like languages. An upper bound lets us call methods and attributes of the bound, but it doesn't let us instantiate new instances of Element. Once we implement reified generics, we'll be able to add a new kind of type constraint to Ceylon. An initialization parameter specification lets us actually instantiate the type parameter.
shared class Factory<out Result>() given Result(String s) { shared Result produce(String string) { return Result(string); } }

A type argument to Result of parameter of type String.

Factory

must be a class with a single initialization

Factory<Hello> = Factory<PersonalizedHello>(); //ok Factory<Hello> = Factory<DefaultHello>(); //compile error

A third kind of type constraint is an enumerated type bound, which constrains the type argument to be one of an enumerated list of types. It lets us write an exhaustive switch on the type parameter:
Value sqrt<Value>(Value x) given Value of Float | Decimal { switch (Value) case (satisfies Float) { return sqrtFloat(x); } case (satisfies Decimal) { return sqrtDecimal(x); } }

This is one of the workarounds we mentioned earlier for Ceylon's lack of overloading. Finally, the fourth kind of type constraint, which is much less common, and which most people find much more confusing, is a lower bound. A lower bound is the opposite of an upper bound. It says that a type parameter is a supertype
54 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

of some other type. There's only really one situation where this is useful. Consider adding a union() operation to our Set interface. We might try the following:
shared class Set<out Element>(Element... elements) given Element satisfies Equality { ...

shared Set<Element> union(Set<Element> set) { return .... } }

//compile error

This doesn't compile because we can't use the covariant type parameter T in the type declaration of a method parameter. The following declaration would compile:
shared class Set<out Element>(Element... elements) given Element satisfies Equality { ...

shared Set<Object> union(Set<Object> set) { return .... } }

But, unfortunately, we get back a Set<Object> no matter what kind of set we pass in. A lower bound is the solution to our dilemma:
shared class Set<out Element>(Element... elements) given Element satisfies Equality { ...

shared Set<UnionElement> union(Set<UnionElement> set) given UnionElement abstracts Element { return ... } }

With type inference, the compiler chooses an appropriate type argument to UnionElement for the given argument to union():
55 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Set<String> strings = Set("abc", "xyz") ; Set<String> moreStrings = Set("foo", "bar", "baz"); Set<String> allTheStrings = strings.union(moreStrings); Set<Decimal> decimals = Set(1.2.decimal, 3.67.decimal) ; Set<Float> floats = Set(0.33, 22.0, 6.4); Set<Number> allTheNumbers = decimals.union(floats); Set<Hello> hellos = Set( DefaultHello(), PersonalizedHello(name) ); Set<Object> objects = Set("Gavin", 12, true); Set<Object> allTheObjects = hellos.union(objects);

56 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

7. Attributes and locals, control structures, and imports


Attributes and locals
In Java, a field of a class is quite easily distinguished from a local constant or variable of a method or constructor. Ceylon doesn't really make this distinction very strongly. An attribute is really just a local that happens to be captured by someshared declaration. Here,
count

is a local variable of the initializer of

Counter:

class Counter() { variable Natural count := 0; }

But in the following two examples,


class Counter() { shared variable Natural count := 0; } class Counter() { variable Natural count := 0; shared Natural inc() { return ++count; } }

count

is an attribute:

This might seem a bit strange at first, but it's really just how closure works. The same behavior applies to locals inside a method. Methods can't declare shared members, but they can return an object that captures a local:
interface Counter { shared formal Natural inc(); } Counter createCounter() { variable Natural count := 0; object counter satisfies Counter { shared actual Natural inc() { return ++count; } } return counter;
57 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Even though we'll continue to use the words local and attribute, keep in mind that there's no really strong distinction between the terms. Any named value might be captured by some other declaration in the same containing scope. (I'm still searching for a really good word to collectively describe attributes and locals.)

Variables
Ceylon encourages you to use immutable attributes as much as possible. An immutable attribute has its value specified when the object is initialized, and is never reassigned.
class Reference<Value>(Value x) { shared Value value = x; }

If we want to be able to assign a value to a simple attribute or local we need to annotate it variable:
class Reference<Value>(Value x) { shared variable Value value := x; }

Notice the use of := instead of = here. This is important! In Ceylon, specification of an immutable value is done using =. Assignment to a variable attribute or local is considered a different kind of thing, always performed using the := operator. The = specifier is not an operator, and can never appear inside an expression. It's just a punctuation character. The following code is not only wrong, but even fails to parse:
if (x=true) { ... } //compile error

Setters
If we want to make an attribute with a getter mutable, we need to define a matching setter. Usually this is only useful if you have some other internal attribute you're trying to set the value of indirectly.

58 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Suppose our class has the following simple attributes, intended for internal consumption only, so un-shared:
variable String? firstName := null; variable String? lastName := null;

(Remember, Ceylon never automatically initializes attributes to

null.)

Then we can abstract the simple attribute using a second attribute defined as a getter/setter pair:
shared String fullName { return " ".join(coalesce(firstName,lastName)); } shared assign fullName { Iterator<String> tokens = fullName.tokens(); firstName := tokens.head; lastName := tokens.rest.head; }

A setter is identified by the keyword assign in place of a type declaration. (The type of the matching getter determines the type of the attribute.) Yes, this is a lot like a Java get/set method pair, though the syntax is significantly streamlined. But since Ceylon attributes are polymorphic, and since you can redefine a simple attribute as a getter or getter/setter pair without affecting clients that call the attribute, you don't need to write getters and setters unless you're doing something special with the value you're getting or setting.

Control structures
Ceylon has five built-in control structures. There's nothing much new here for Java or C# developers, so I'll just give a few quick examples without much additional commentary. However, one thing to be aware of is that Ceylon doesn't allow you to omit the braces in a control structure. The following doesn't parse:
if (x>100) bigNumber();

You are required to write:


if (x>100) { bigNumber(); }

59 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

OK, so here's the examples. The


if (x>100)) { bigNumber(x); } else if (x>1000) { reallyBigNumber(x); } else { littleNumber(); }

if/else

statement is totally traditional:

The switch/case statement eliminates C's much-criticized fall through behavior and irregular syntax:
switch (x<=>100) case (smaller) { littleNumber(); } case (equal) { oneHundred(); } case (larger) { bigNumber(); }

The for loop has an optional fail block, which is executed when the loop completes normally, rather than via a return orbreak statement. There's no Cstyle for.
Boolean minors; for (Person p in people) { if (p.age<18) { minors = true; break; } } fail { minors = false; }

The

while

and

do/while

loops are traditional.

variable local it = names.iterator(); while (exists String name = it.head) { writeLine(name); it:=it.tail; }

The

try/catch/finally

statement works like Java's:


60 | P a g e

Source: http://relation.to/tag/Introduction+to+Ceylon

try { message.send(); } catch (ConnectionException|MessageException e) { tx.setRollbackOnly(); }

And

try

supports a resource expression similar to Java 7.

try (Transaction()) { try (Session s = Session()) { s.persist(person); } }

Sequenced parameters
A sequenced parameter of a method or class is declared using an ellipsis. There may be only one sequenced parameter for a method or class, and it must be the last parameter.
void print(String... strings) { ... }

Inside the method body, the parameter


void print(String... strings) { for (String string in strings) { write(string); } writeLine(); }

strings

has type

String[].

A slightly more sophisticated example is the coalesce() method we saw above. coalesce() accepts X?[] and eliminates nulls, returning X[], for any type X. Its signature is:
shared Value[] coalesce<Value>(Value?... sequence) { ... }

Sequenced parameters turn out to be especially interesting when used in named argument lists for defining user interfaces or structured data.

Packages and imports


There's no special package statement in Ceylon. The compiler determines the package and module to which a toplevel program element belongs by the
61 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

location of the source file in which it is declared. A class named Hello in the package org.jboss.hellomust be defined in the file org/jboss/hello/Hello.ceylon. When a source file in one package refers to a toplevel program element in another package, it must explicitly import that program element. Ceylon, unlike Java, does not support the use of qualified names within the source file. We can't writeorg.jboss.hello.Hello in Ceylon. The syntax of the import statement is slightly different to Java. To import a program element, we write:
import org.jboss.hello { Hello }

To import several program elements from the same package, we write:


import org.jboss.hello { Hello, defaultHello, PersonalizedHello }

To import all toplevel program elements of a package, we write:


import org.jboss.hello { ... }

To resolve a name conflict, we can rename an imported declaration:


import org.jboss.hello { local Hi = Hello, ... }

We think renaming is a much cleaner solution than the use of qualified names.

62 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

8. Higher order functions


First class and higher order functions
Ceylon isn't a functional language: it has variable attributes and so methods can have side effects. But Ceylon does let you use functions as values, which in some people's eyes makes the language a kind of hybrid. I'm not so sure about that. There's actually nothing at all new about having functions-as-values in an object oriented language for example, Smalltalk, one of the first and still one of the cleanest object oriented languages, was built around this idea. (To my eyes, true functionalprogramming is more about what you can't do mutate values than what you can do.) Anyway, Ceylon, like Smalltalk and a number of other object oriented languages, lets you treat a function as an object and pass it around the system. In this installment, we're going to discuss Ceylon's support for first class and higher order functions. First class function support means the ability to treat a function as a value. A higher order function is a function that accepts other functions as arguments, or returns another function. It's clear that these two ideas go hand-in-hand, so I'll just talk about higher order function support from now on. A quick disclaimer: none of the things in this installment have actually been implemented in the compiler yet.

Representing the type of a function


Ceylon is a (very) statically typed language. So if we're going to treat a function as a value, the very first question that arises is: what is the type of the function? We need a way to encode the return type and parameter types of a function into the type system. Remember that Ceylon doesn't have primitive types. A strong design principle is that every type should be representable within the type system as a class or interface declaration. I suppose Ceylon could have gone down the road of some functional languages, and represented all functions with multiple parameters in curried form. So
Natural sum(Natural x, Natural y) { ... }

would just be an abbreviation of


Natural sum(Natural x)(Natural y) { ... }

63 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

i.e. a function with one parameter that returns another function with one parameter. Then we could have represented the type of the function like this:
Function<Natural,Function<Natural,Natural>>

But we've decided not to go down this path. Some other languages have chosen to have a separate type for each function arity. So there's F0<R>, F1<R,P1>, F2<R,P1,P2>,F3<R,P1,P2,P3>, etc. But this solution feels kinda .... lame. Worse, it doesn't allow us to abstract over all function types, building up abstractions like Method and Class, etc. We're going to need to be able to do that kind of thing when we get to discussing the typesafe metamodel. In Ceylon, a single type following:
Callable

abstracts all functions. It's declaration is the

shared interface Callable<out Result, Argument...> {}

The syntax P... is called a sequenced type parameter. By analogy with a sequenced parameter, which accepts zero or more values as arguments, a sequenced type parameter accepts zero or more types as arguments. The type parameter Resultrepresents the return type of the function. The sequenced type parameter Argument... represents the parameter types of the function. So the type of
sum

in Ceylon is:

Callable<Natural, Natural, Natural>

What about void functions? Well, remember that way back in Part 1 we said that the return type of a void function is Void. So the type of a function like print() is:
Callable<Void,String>

Representing the type of a method


Here we've been discussing first class functions. But in Ceylon all named declarations are first class. That is to say, they all have a reified metamodel representable within the type system. For example, we could represent the type of a method like this:
shared interface Method<out Result, in Instance, Argument...> satisfies Callable<Callable<Result,Argument...>, Instance> {}

64 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Where Instance is the type that declares the method. So the type of the method iterator() of Iterable<String> would be:
Method<Iterator<String>, Iterable<String>>

And the type of the method

compare()

of

Comparable<Natural>

would be:

Method<Comparison,Comparable<Natural>,Natural>

Notice that we've declared a method to be a function that accepts a receiver object and returns a function. As a consequence of this, an alternative method invocation protocol is the following:
Iterable<String>.iterator(strings)(); Comparable<Natural>.compare(0)(num);

Don't worry if you can't make sense of that right now. And actually I'm skipping over some details here, that's not quite exactly how Method is defined. But we'll come back to this in a future installment. Let's get back to today's topic.

Defining higher order functions


We now have enough machinery to be able to write higher order functions. For example, we could create a repeat() function that repeatedly executes a function.
void repeat(Natural times, Callable<Void,Natural> perform) { for (Natural i in 1..times) { perform(i); } }

And call it like this:


void print(Natural n) { writeLine(n); } repeat(10, print);

Which would print the numbers

to

10

to the console.

There's one problem with this. In Ceylon, as we'll see later, we often call functions using named arguments, but the Callabletype does not encode the names of the function parameters. So Ceylon has an alternative, more elegant, syntax for declaring a parameter of type Callable:
void repeat(Natural times, void perform(Natural n)) {
65 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

for (Natural i in 1..times) { perform(i); } }

I find this version also slightly more readable and more regular. This is the preferred syntax for defining higher-order functions.

Function references
When a name of a function appears without any arguments, like print does above, it's called a function reference. A function reference is the thing that really has the type Callable. In this case, print has the type Callable<Void,Natural>. Now, remember how we said that Void is both the return type of a void method, and also the logical root of the type hierarchy? Well that's useful here, since it means that we can assign a function with a non-Void return type to any parameter which expects a void method:
Boolean attemptPrint(Natural n) { try { writeLine(n); return true; } catch (Exception e) { return false; } } repeat(10, attemptPrint);

Another way we can produce a function reference is by partially applying a method to a receiver expression. For example, we could write the following:
class Hello(String name) { shared void say(Natural n) { writeLine("Hello, " name ", for the " n "th time!"); } } repeat(10, Hello("Gavin").say);

Here the expression Hello("Gavin").say has the same type as a Callable<Void,Natural>.

print

above. It is

66 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

More about higher-order functions


Let's see a more practical example, which mixes both ways of representing a function type. Suppose we have some kind of user interface component which can be observed by other objects in the system. We could use something like Java'sObserver/Observable pattern:
shared interface Observer { shared formal void observe(Event event); } shared abstract class Component() { OpenList<Observer> observers = OpenList<Observer>();

shared void addObserver(Observer o) { observers.append(o); }

shared void fire(Event event) { for (Observer o in observers) { o.observe(event); } } }

But now all event observers have to implement the interface Observer, which has just one method. Why don't we cut out the interface, and let event observers just register a function object as their event listener? In the following code, we define theaddObserver() method to accept a function as a parameter.
shared abstract class Component() { OpenList<Callable<Void,Event>> observers = OpenList<Callable<Void,Event>>();

shared void addObserver(void observe(Event event)) { observers.append(observe); }

shared void fire(Event event) { for (void observe(Event event) in observers) { observe(event); } }
67 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Here we see the difference between the two ways of specifying a function type:

is more readable in parameter lists, where the name of the parameter, but
void observe(Event event) Callable<Void,Event>

observe

is

is useful as a generic type argument.

Now, any event observer can just pass a reference to one of its own methods to addObserver():
shared class Listener(Component component) { void onEvent(Event e) { //respond to the event ... } component.addObserver(onEvent); ... }

When the name of a method appears in an expression without a list of arguments after it, it is a reference to the method, not an invocation of the method. Here, the expression onEvent is an expression of type Callable<Void,Event> that refers to the method onEvent(). If onEvent() were shared, we could even wire together the Component and Listener from some other code, to eliminate the dependency of Listener on Component:
shared class Listener() { shared void onEvent(Event e) { //respond to the event ... } ... } void listen(Component component, Listener listener) {
68 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

component.addObserver(listener.onEvent); }

Here, the syntax listener.onEvent is a kind of partial application of the method onEvent(). It doesn't cause the onEvent()method to be executed (because we haven't supplied all the parameters yet). Rather, it results in a function that packages together the method reference onEvent and the method receiver listener. It's also possible to declare a method that returns a function. A method that returns a function has multiple parameter lists. Let's consider adding the ability to remove observers from a Component. We could use a Subscription interface:
shared interface Subscription { shared void cancel(); } shared abstract class Component() { ...

shared Subscription addObserver(void observe(Event event)) { observers.append(observe); object subscription satisfies Subscription { shared actual void cancel() { observers.remove(observe); } } return subscription; } ... }

But a simpler solution might be to just eliminate the interface and return the cancel() method directly:
shared abstract class Component() { ...

shared void addObserver(void observe(Event event))() { observers.append(observe); void cancel() { observers.remove(observe);


69 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

} return cancel; } ... }

Note the second parameter list of

addObserver().

Here, we define a method cancel() inside the body of the addObserver() method, and return a reference to the inner method from the outer method. The inner method cancel() can't be called directly from outside the body of the addObserver()method, since it is a block local declaration. But the reference to cancel() returned by addObserver() can be called by any code that obtains the reference. Oh, in case you're wondering, the type of the method addObserver() isCallable<Callable<Void>,Component,Callable<Void,Event>>. Notice that cancel() is able to use the parameter observe of addObserver(). We say that the inner method receives a closure of the non-variable locals and parameters of the outer method just like a method of a class receives a closure of the class initialization parameters and locals of the class initializer. In general, any inner class, method, or attribute declaration always receives the closure of the members of the class, method, or attribute declaration in which it is enclosed. This is an example of how regular the language is. We could invoke our method like this:
addObserver(onEvent)();

But if we were planning to use the method in this way, there would be no good reason for giving it two parameter lists. It's much more likely that we're planning to store or pass the reference to the inner method somewhere before invoking it.
void cancel() = addObserver(onEvent); ... cancel();

The first line demonstrates how a method can be defined using a = specification statement, just like a simple attribute definition. The second line of code simply invokes the returned reference to cancel().
70 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

We've already seen how an attribute can be defined using a block of code. Now we see that a method can be defined using a specifier. So, if you like, you can start thinking of a method as an attribute of type Callable an attribute with parameters. Or if you prefer, you can think of an attribute as member with zero parameter lists, and of a method as a member with one or more parameter lists. Either kind of member can be defined by reference, using =, or directly, by specifying a block of code to be executed. Cool, huh? That's more regularity.

71 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

9. Named arguments and the builder syntax


Named arguments
Consider the following method:
void printf(OutputStream to, String format, Object... values) { ... }

(Remember, the last parameter is a sequenced parameter which accepts multiple arguments, just like a Java varargsparameter.) We've seen lots of examples of invoking a method or instantiating a class using a familiar C-style syntax where arguments are delimited by in parentheses and separated by commas. Arguments are matched to parameters by their position in the list. Let's see just one more example, just in case:
printf(process, "Thanks, %s. You have been charged %.2f. Your confirmation number is %d.", user.name, order.total, order.confimationNumber);

This works fine, I suppose. However, Ceylon provides an alternative method invocation protocol that is usually easier to read when there are more than one or two arguments:
printf { to = process; format = "Thanks, %s. You have been charged %.2f. Your confirmation number is %d."; user.name, order.total, order.confimationNumber };

This invocation protocol is called a named argument list. We can recognize a named argument list by the use of braces as delimiters instead of parentheses. Notice that arguments are separated by semicolons, except for arguments to the sequenced parameter, which are separated by commas. We explicitly specify the name of each parameter, except for the sequenced parameter, whose arguments always appear at the end of the named parameter list. Note that it's also acceptable to call this method like this, passing a sequence to the named value parameter:
printf { to = process; format = "Thanks, %s. You have been charged %.2f. Your confirmation number is %d."; values = { user.name, order.total, order.confimationNumber };
72 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

};

We usually format named argument invocations across multiple lines.

Declarative object instantiation syntax


Named arguments are very commonly used for building graphs of objects. Therefore, Ceylon provides a special abbreviated syntax that simplifies the declaration of an attribute getter, named parameter, or method that builds an object by specifying named arguments to the class initializer. We're allowed to abbreviate an attribute definition of the following form:
Payment payment = Payment { method = user.paymentMethod; currency = order.currency; amount = order.total; };

or a named argument specification of this form:


payment = Payment { method = user.paymentMethod; currency = order.currency; amount = order.total; };

to the following more declarative (and less redundant) style:


Payment payment { method = user.paymentMethod; currency = order.currency; amount = order.total; }

We're even allowed to write a method of the following form:


Payment createPayment(Order order) { return Payment { method = user.paymentMethod; currency = order.currency; amount = order.total; }; }

73 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

using the following abbreviated syntax:


Payment createPayment(Order order) { method = user.paymentMethod; currency = order.currency; amount = order.total; }

Perhaps you're worried that this looks like a method that assigns the values of three attributes of the declaring class, rather than a shortcut syntax for a named argument instantiation of the Payment class. And that's a very fair point. To a Java developer, that is what it looks like. There's two things that should alert you to what's really going on. The above method:

has no

return

statement, but it's not declared


=

void,

and
:=

contains a list of expressions.

specification statements instead of

assignment

Once you're used to Ceylon's more flexible syntax, these differences will usually stand out immediately.

More about named arguments


The following classes define a data structure for building tables:
class Table(String title, Natural rows, Border border, Column... columns) { ... } class Column(String heading, Natural width, String content(Natural row)) { ... } class Border(Natural padding, Natural weight) { ... }

Of course, we could built a

Table

using positional argument lists:

String x(Natural row) { return row.string; } String xSquared(Natural row) { return (row**2).string; } Table table = Table("Squares", 5, Border(2,1), Column("x",10, x), Column("x**2",12, xSquared));

However, it's far more common to use named arguments to build a complex graph of objects. In this section we're going to meet some new features of named argument lists, that make it especially convenient to build object graphs. First, note that the syntax we've already seen for specifying a named argument value looks exactly like the syntax for refining a formal attribute. If you think about it, taking into account that a method parameter may accept references to
74 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

other methods, the whole problem of specifying values for named parameters starts to look a lot like the problem of refining abstract members. Therefore, Ceylon will let us reuse much of the member declaration syntax inside a named argument list. (But note that this has not yet been implemented in the compiler.) It's legal to include the following constructs in a named argument list:

method declarations specify the value of a parameter that accepts a function, (anonymous class) declarations are most useful for specifying the value of a parameter whose type is an interface or abstract class, and
object

getter declarations lets us compute the value of an argument inline. This helps explain why named argument lists are delimited by braces: the fully general syntax for a named argument list is very, very close to the syntax for a class, method, or attribute body. Notice, again, how flexibility derives from language regularity. So we could rewrite the code that builds a
Table table = Table { title="Squares"; rows=5; border = Border { padding=2; weight=1; }; Column { heading="x"; width=10; String content(Natural row) { return row.string; } }, Column { heading="x**2"; width=12; String content(Natural row) { return (row**2).string; } } };
75 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Table

as follows:

Notice that we've specified the value of the parameter named usual syntax for declaring a method. Even better, our example can be abbreviated like this:
Table table { title="Squares"; rows=5; Border border { padding=2; weight=1; } Column { heading="x"; width=10; String content(Natural row) { return row.string; } }, Column { heading="x**2"; width=10; String content(Natural row) { return (row**2).string; } } }

content

using the

Notice how we've transformed our code from a form which emphasized invocation into a form that emphasizes declaration of a hierarchical structure. Semantically, the two forms are equivalent. But in terms of readability, they are very different. We could put the above totally declarative code in a file by itself and it would look like some kind of mini-language for defining tables. In fact, it's executable Ceylon code that may be validated for syntactic correctness by the Ceylon compiler and then compiled to Java bytecode. Even better, the Ceylon IDE (when it exists) will provide authoring support for our mini-language. In complete contrast to the DSL support in some dynamic languages, any Ceylon DSL is completely typesafe! You can think of the definition of the Table, Column and Border classes as defining the schema or grammar of the mini-language. (In fact, they are really defining the syntax tree for the minilanguage.)

76 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Now let's see an example of a named argument list with an inline getter declaration:
shared class Payment(PaymentMethod method, Currency currency, Float amount) { ... } Payment payment { method = user.paymentMethod; currency = order.currency; Float amount { variable Float total := 0.0; for (Item item in order.items) { total += item.quantity * item.product.unitPrice; } return total; } }

Finally, here's an example of a named argument list with an inline object declaration:
shared interface Observable { shared void addObserver(Observer<Bottom> observer) { ... } } shared interface Observer<in Event> { shared formal on(Event event); } observable.addObserver { object observer satisfies Observer<UpdateEvent> { shared actual void on(UpdateEvent e) { writeLine("Update:" + e.string); } } };

Note that Observer<T> is assignable to Observer<Bottom> for any type T, since Observer<T> is contravariant in its type parameter T. If this doesn't make sense, please read these two sections again. ;-) Of course, as we saw in Part 8, a better way to solve this problem might be to eliminate the Observer interface and pass a method directly:
shared interface Observable { shared void addObserver<Event>(void on(Event event)) { ... } } observable.addObserver {
77 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

void on(UpdateEvent e) { writeLine("Update:" + e.string); } };

A quick tangent here: note that we need a type parameter T of the method addObserver() here only because Ceylon inherits Java's limitation that function types are nonvariant in their parameter types. This is actually pretty unnatural. We should probably eventually come up with a workaround to make function types contravariant in their parameter types, allowing us to write:
shared interface Observable { shared void addObserver(void on(Bottom event)) { ... } }

Defining user interfaces


One of the first modules we're going to write for Ceylon will be a library for writing HTML templates in Ceylon. A fragment of static HTML would look something like this:
Html { Head head { title = "Hello World"; cssStyleSheet = 'hello.css'; } Body body { Div { cssClass = "greeting"; "Hello World" }, Div { cssClass = "footer"; "Powered by Ceylon" } } }

A complete HTML template might look like this:


import ceylon.html { ... } doc "A web page that displays a greeting" page '/hello.html' Html hello(Request request) {
78 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Head head { title = "Hello World"; cssStyleSheet = 'hello.css'; }

Body body { Div { cssClass = "greeting"; Hello( request.parameters["name"] ).greeting }, Div { cssClass = "footer"; "Powered by Ceylon" } } };

79 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

10. Basic types and operators


An overview of the language module
The module ceylon.language contains classes and interfaces that are referred to in the language specification, other declarations they refer to, and a number of related declarations. Let's meet the main characters. Just like Java, Ceylon has a class named
shared abstract class Object() extends Void() { Object.

doc "A developer-friendly string representing the instance." shared formal String string;

doc "Determine if this object belongs to the given Category or is produced by the iterator of the given Iterable object." shared Boolean element(Category|Iterable<Equality> category) { switch (category) case (is Category) { return category.contains(this); } case (is Iterable<Equality>) { if (is Equality self = this) { for (Equality x in category) { if (x==self) { return true; } } fail { return false; } } else { return false; } } } }

In Ceylon, Object isn't the root of the type system. An expression of type Object has a definite, well-defined, non-null value. As we've seen, the
80 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Ceylon type system can also represent some more exotic types, for example Nothing, which is the type ofnull. Therefore, Ceylon's Object has a superclass, named Void, which we already met in Part 1. All Ceylon types are assignable toVoid. Expressions of type Void aren't useful for very much, since Void has no members or operations. You can't even narrow an expression of type Void to a different type. The one useful thing you can do with Void is use it to represent the signature of a method when you don't care about the return type, since a method declared void is considered to have return type Void, as we saw in Part 8. As we also saw in Part 1, the type Nothing directly extends represent well-defined values extend Object, including:

Void.

All types that

user-written classes, all interfaces, and the types that are considered primitive in Java, such as Integer, Float and Character. Since an expression of type Object always evaluates to a definite, well-defined value, it's possible to obtain the runtime type of an Object, or narrow an expression of type Object to a more specific type.

Equality and identity


On the other hand, since Object is a supertype of types like Float which are passed by value at the level of the Java Virtual Machine, you can't use the === operator to test the identity of two values of type Object. Instead, there is a subclass of Object, named IdentifiableObject, which represents a type which is always passed by reference. The === operator accepts expressions of type IdentifiableObject. It's possible for a user-written class to directly extend Object, but most of the classes you write will be subclasses of IdentifiableObject. All classes with variable attributes must extendIdentifiableObject.
shared abstract class IdentifiableObject() extends Object() satisfies Equality { shared default actual Boolean equals(Equality that) { if (is IdentifiableObject that) { return this===that; } else {
81 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

return false; } }

shared default actual Integer hash { return identityHash(this); }

shared default actual String string { ... } }

defines a default implementation of the interface Equality, which is very similar to the equals() andhashCode() methods defined by java.lang.Object.
IdentifiableObject shared interface Equality {

shared formal Boolean equals(Equality that);

shared formal Integer hash; }

Just like in Java, you can refine this default implementation in your own classes. This is the normal way to get a customized behavior for the == operator, the only constraint being, that for subtypes of IdentifiableObject, x===y should imply x==y equality should be consistent with identity. Occasionally that's not what we want. For example, for numeric types, I don't care whether a value is of class Natural, Integer, or Whole when comparing it to 0. Fortunately, numeric types extend Object directly, and are not subject to the additional constraints defined by IdentifiableObject. Thus, Ceylon is able to capture within the type system much of the behavior that Java introduces by fiat special-case rules in the language definition.

Operator polymorphism
Ceylon discourages the creation of intriguing executable ASCII art. Therefore, true operator overloading is not supported by the language. Instead, almost every operator (every one except the primitive ., (), is, and := operators) is
82 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

considered a shortcut way of writing some more complex expression involving other operators and ordinary method calls. For example, the< operator is defined in terms of the interface Comparable<Other>, which we met in Part 5, and which has a method namedsmallerThan(), which is in turn defined in terms of another method named compare().
x<y

means, by definition,
x.smallerThan(y)

The equality operator == is defined in terms of the interface a method named equals().
x==y

Equality,

which has

means, by definition,
x.equals(y)

Therefore, it's easy to customize operators like < and == with specific behavior for our own classes, just by implementing or refining methods like compare() and equals(). Thus, we say that operators are polymorphic in Ceylon. Apart from Comparable and Equality, which provide the underlying definition of comparison and equality operators, the following interfaces are also important in the definition of Ceylon's polymorphic operators:

Summable

supports the infix

operator,
+

Invertable Numeric Slots

supports the prefix

and

operators,

supports the other basic arithmetic operators, supports the comparison operators, and
Sequence

supports bitwise operators, support indexing and subrange operators, and

Comparable

Correspondence Boolean

is the basis of the logical operators.

Operator polymorphism is a little more flexible than you might imagine. Here's a quick example of this.

The Slots interface


83 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

The interface Slots is an abstraction of the idea of a set of slots which may each hold true or false. The bitwise operators &,|, and ~ are defined in terms of this interface. The most obvious subtype of Slots would be a Byte class, where the slots are the eight binary digits. But the interface Set from the collections module also extends Slots. The slots of a Set are values which may or may not belong to the set. A slot holds true if the value it represents belongs to the Set. The practical value of this is to allow the use of the operator | for set union, the operator & for set intersection, and the infix ~ operator for set complement.
Set<Person> children = males|females ~ adults;

Yes, I realize that these aren't the traditional symbols representing these operations. But if you think carefully about the definition of these operations, I'm pretty sure you'll agree that these symbols are reasonable. We could even define a Permission class that implements write things like permissions&(read|execute).
Slots,

allowing us to

Numeric types
As we've mentioned several times before, Ceylon doesn't have anything like Java's primitive types. The types that represent numeric values are just ordinary classes. Ceylon has fewer built-in numeric types than other C-like languages:

Natural Integer Float Whole

represents the unsigned integers and zero, represents signed integers,

represents floating point approximations to the real numbers, represents arbitrary-precision signed integers, and represents arbitrary-precision and arbitrary-scale decimals.

Decimal

Natural, Integer

and Float have 64-bit precision by default. Eventually, you'll be able to specify that a value has 32-bit precision by annotating it small. But note that this annotation is really just a hint that the compiler is free to ignore (and it currently does).

Numeric literals
There are only two kinds of numeric literals: literals for for Floats:
Naturals,

and literals

84 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Natural one = 1; Float oneHundredth = 0.01; Float oneMillion = 1.0E+6;

The digits of a numeric literal may be grouped using underscores. If the digits are grouped, then groups must contain exactly three digits.
Natural twoMillionAndOne = 2_000_001; Float pi = 3.141_592_654;

A very large or small numeric literals may be qualified by one of the standard SI unit prefixes: m, u, n, p, f, k, M, G, T, P.
Float red = 390.0n; //n (nano) means E-9 Float galaxyDiameter = 900.0P; //P (peta) means E15 Float hydrogenRadius = 25.0p; //p (pico) means E-12 Float usGovDebt = 14.33T; //T (tera) means E12 Float brainCellSize = 4.0u; //u (micro) means E-6 Natural deathsUnderCommunism = 94M; //M (mega) means E6

Numeric widening
I mentioned earlier that Ceylon doesn't have implicit type conversions, not even built-in conversions for numeric types. Assignment does not automatically widen (or narrow) numeric values. Instead, we need to call one of the operations (well, attributes, actually) defined by the interface Number.
Whole zero = 0.whole; //explicitly widen from Natural Decimal half = 0.5.decimal; //explicitly widen from Float

Usefully, the unary prefix operators


Integer negativeOne = -1; Integer three = +3;

and

always widen

Natural

to

Integer:

You can use all the operators you're used to from other C-style languages with the numeric types. You can also use the **operator to raise a number to a power:
Float diagonal = (length**2.0+width**2.0)**0.5;

Of course, if you want to use the increment ++ operator, decrement -- operator, or one of the compound assignment operators such as +=, you'll have to declare the value variable.
85 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Since it's quite noisy to explicitly perform numeric widening in numeric expressions, the numeric operators automatically widen their operands, so we could write the expression above like this:
Float diagonal = (length**2+width**2)**(1.0/2);

The built-in widening conversions are the following:



Natural Integer Float Whole

to to

Integer, Float, Whole, Float, Whole,

or

Decimal

or

Decimal

to to

Decimal Decimal

But these conversions aren't defined by special-case rules in the language specification.

Numeric operator semantics


Operators in Ceylon are, in principle, just abbreviations for some expression involving a method call. So the numeric types all implement the Numeric interface, refining the methods plus(), minus(), times(), divided() and power(), and the Invertableinterface, refining inverse. The numeric operators are defined in terms of these methods of Numeric. The numeric types also implement the interface Castable, which enables the widening conversions we just mentioned.
shared interface Castable<in Types> { shared formal CastValue as<CastValue>() given CastValue satisfies Types; }

The type parameter Types uses a special trick. The argument to Types should be the union of all types to which the implementing type is castable. For example, simplifying slightly the definitions in the language module:
shared class Natural(...) extends Object() satisfies Castable<Natural|Integer|Float|Whole|Decimal> & Numeric<Natural> & Invertable<Integer> { ... }
86 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

shared class Integer(...) extends Object() satisfies Castable<Integer|Float|Whole|Decimal> & Numeric<Integer> & Invertable<Integer> { ... } shared class Float(...) extends Object() satisfies Castable<Float|Decimal> & Numeric<Float> & Invertable<Float> { ... }

These declarations tell us that Integer can be widened to Float, Whole, or but that Float can only be widened toDecimal. So we can infer that the expression -1 * 0.4 is of type Float.

Decimal,

Therefore, the definition of a numeric operator like * can be represented, completely within the type system, in terms ofNumeric and Castable:
Result product<Left,Right,Result>(Left x, Right y) given Result of Left|Right satisfies Numeric<Result> given Left satisfies Castable<Result> & Numeric<Left> given Right satisfies Castable<Result> & Numeric<Right> { return x.as<Result>().times(y.as<Result>()); }

Don't worry too much about the performance implications of all this in practice, the compiler is permitted to optimize the types Natural, Integer, and Float down to the virtual machine's native numeric types. The value of all this apart from eliminating special cases in the language definition and type checker is that a library can define its own specialized numeric types, without losing any of the nice language-level syntax support for numeric arithmetic and numeric widening conversions.

87 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

11. Initialization
Self references and outer instance references
Ceylon features the keywords this and super, which refer to the current instance of a class the receiving instance of an operation (method invocation, member class instantiation, or attribute evaluation/assignment), within the body of the definition of the operation. The semantics are exactly the same as what you're used to in Java. In particular, a reference to a member of super always refers to a member of a superclass. There is currently no syntax defined for references to a concrete member of a superinterface. In addition to this and super, Ceylon features the keyword outer, which refers to the parent instance of the current instance of a nested class.
class Parent(String name) { shared String name = name; shared class Child(String name) { shared String name = outer.name + "/" + name; shared Parent parent { return outer; } } }

There are some restrictions on the use of explore below.

this, super,

and

outer,

which we'll

Multiple inheritance and linearization


There's a good reason why superinterface.
super

always refers to a superclass, and never to a

Ceylon features a restricted kind of multiple inheritance often called mixin inheritance. Some languages with multiple inheritance or even mixin inheritance feature so-called depth-first member resolution or linearization where all supertypes of a class are arranged into a linear order. We believe that this model is arbitrary and fragile. Ceylon doesn't perform any kind of linearization of supertypes. The order in which types appear in the satisfies clause isnever significant. The only way one supertype can take precedence over another supertype is if the first supertype is a subtype of the second supertype. The only way a member of one supertype can take precedence over a member of another supertype is if the first member refines the second member.
88 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

In our view, there's no non-fragile basis for deciding that one type specializes another type unless the first type is explicitly defined to be a subtype of the second. There's no non-fragile basis for deciding that one operation is more specific than another operation unless the first operation is explicitly declared to refine the second. For a similar reason, interfaces shouldn't be able to define initialization logic. There's no non-fragile way to define the ordering in which supertype initializers are executed in a multiple-inheritance model. This is the basic reason why interfaces are stateless in Ceylon. (Note that these arguments are even stronger in the case of adapter introduction, where linearization or statefulness would beeven more fragile.) So Ceylon is more restrictive than some other languages here. But we think that this restriction makes a subtype less vulnerable to breakage due to changes in its supertypes.

Definite assignment and definite initialization


A really nice feature of Java is that the compiler checks that a local variable has definitely been assigned a value before allowing use of the local variable in an expression. So, for example, the following code compiles without error:
String greeting; if (person==me) { greeting = "You're beautiful!"; } else { greeting = "You're ugly!"; } print(greeting);

But the following code results in an error at compile time:


String greeting; if (person==me) { greeting = "You're beautiful!"; } print(greeting); //error: greeting not definitely initialized

Many (most?) languages don't perform this kind of static analysis, which means that use of an uninitialized variable results in an error at runtime instead of compile time.
89 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Unfortunately, Java doesn't do this same kind of static analysis for instance variables, not even for final instance variables. Instead, an instance variable which is not assigned a value in the constructor is initialized to a default value (zero or null). Surprisingly, it's even possible to see this default value for a final instance variable that is eventually assigned a value by the constructor. Consider the following code:
//Java code that prints "null" class Broken { final String greeting; Broken() { print(); greeting = "Hello"; } void print() { System.out.println(greeting); } } new Broken();

This behavior is bad enough in and of itself. But it would be even less acceptable in Ceylon, where most types don't have an acceptable default value. For example, consider the type Person. What would be an acceptable default value of this type? The value null certainly won't do, since it's not even an instance of Person. (It's an instance of Nothing, remember!) I suppose we could say that evaluation of an uninitialized instance variable always results in an immediate runtime exception, but this is really just our old friend NullPointerException creeping back in by the back door, and, well, it's Just Not How We Do Things Around Here. Indeed, few object-oriented languages (i.e. none that I know of) perform the necessary static analysis to ensure definite initialization of instance variables, and I believe that this is perhaps one main reason why object-oriented languages have never featured typesafe handling of null values.

Class bodies
In order to make it possible for the compiler to guarantee definite initialization of attributes, Ceylon imposes some restrictions on the body of a class. (Remember that Ceylon doesn't have constructors!) Actually, to be completely fair, they're not really restrictions at all, at least not from one point of view, since you're actually allowed extra flexibility in the body of a class that you're
90 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

not allowed in the body of method or attribute declarations! But compared to Java, there's some things you're not allowed to do. First, we need to know that the compiler automatically divides the body of the class into two sections: 1. First comes the initializer section, which contains a mix of declarations, statements and control structures. The initializer is executed every time the class is instantiated. 2. Then comes the declaration section, which consists purely of declarations, similar to the body of an interface. Now we're going to introduce some rules that apply to code that appears in each section. The purpose of these rules is to guarantee that an instance variable has had a value specified or assigned before its value is used in an expression. But you don't need to actually explicitly think about these rules when you write code. Only very rarely will you need to think about the initializer section and declaration section in explicit terms. The compiler will let you know when you break the rules, and force you to fix your code.

Initializer section
The initializer section is responsible for initializing the state of the new instance of the class, before a reference to the new instance is available to clients. The declaration section contains members of the class which are only called after the instance has been fully initialized. Consider the following example:
class Hello(String? name) { //initializer section: String greetingForTime { if (morning) { return "Good morning"; } else if (afternoon) { return "Good afternoon"; } else if (evening) { return "Good evening";
91 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

} else { return "Hi"; } }

String greeting; if (exists name) { greeting = greetingForTime + ", " + name; } else { greeting = greetingForTime; } //declaration section:

shared void say() { print(greeting); }

default void print(String message) { writeLine(message); } }

To prevent a reference to a new instance of the class leaking before the new instance has been completely initialized, the language spec defines the following terminology: Within a class initializer, a self reference to the instance being initialized is either:

the expression declaration, or the expression declaration.

this,

unless contained in a nested class contained in a directly nested class

outer,

Now, according to the language spec: A statement or declaration that appears within the initializer of a class may not:

evaluate attributes, invoke methods, or instantiate member classes that are declared later in the body of the class upon the
92 | P a g e

Source: http://relation.to/tag/Introduction+to+Ceylon

instance that is being initialized, including upon a self reference to the instance being initialized.

pass a self reference to the instance being initialized as an argument of an instantiation or method invocation or as the value of an attribute assignment or specification.
return

a self reference to the instance being initialized.

evaluate attributes, invoke methods, or instantiate member classes declared in the declaration section of a superclass of the instance being initialized, including upon a self reference to the instance being initialized. invoke or evaluate a formal member of the instance being initialized, including upon a self reference to the instance being initialized. invoke or evaluate a default member of the instance that is being initialized, except via the specialsuper self reference.

Declaration section
The declaration section contains the definition of members that don't hold state, and that are never called until the instance to which they belong has been completely initialized. According to the language spec: [The declaration section] may not contain:

a statement or control structure, unless it is nested inside a method, attribute, nested class, or nested interface declaration, a declaration with a specifier or initializer, unless it is nested inside a method, attribute, nested class, or nested interface declaration, an
object

declaration with a non-empty initializer section, or

a specification or initialization statement for a member of the instance being initialized.

However, the declarations in this second section may freely use this and super, and may invoke any method, evaluate any attribute, or instantiate any member class of the class or its superclasses. Furthermore, the usual restriction that a declaration may only be used by code that appears later in the block containing the declaration is relaxed.

93 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Note that the rules governing the declaration section of a class body are essentially the same rules governing the body of an interface. That makes sense, because interfaces don't have initialization logic what interfaces and declaration sections have in common is statelessness.

Circular references
Unfortunately, these rules make it a little tricky to set up circular references between two objects without resort to non-variable attributes. This is a problem Ceylon has in common with functional languages, which also emphasize immutability. We can't write the following code in Ceylon:
abstract class Child(Parent p) { shared formal Parent parent = p; } class Parent() { shared Child child = Child(this); //compile error (this passed as argument in initializer section) }

Eventually, Ceylon will probably need some specialized machinery for dealing with this problem, but for now, here is a partial solution:
abstract class Child() { shared formal Parent parent; } class Parent() { shared object child extends Child() { shared actual parent { return outer; } } }

Definite initialization of methods


Ceylon lets us separate the declaration of a method defined using a method reference from the actual specification statement that specifies the method reference.
Float x = ... ; Float op(Float y); switch (symbol)
94 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

case ("+") { op = x.plus; } case ("-") { op = x.minus; } case ("*") { op = x.times; } case ("/") { op = x.divided; }

The rules for definite initialization of locals and attributes also apply to methods defined using a specification statement.

Definite return
While we're on the topic, it's worth noting that the Ceylon compiler, just like the Java compiler, also performs definite return checking, to ensure that a method or getter always has an explicitly specified return value. So, this code compiles without error:
String greeting { if (person==me) { return "You're beautiful!"; } else { return "You're ugly!"; } }

But the following code results in an error at compile time:


view sourceprint?
String greeting { //error: greeting does not definitely return if (person==me) { return "You're beautiful!"; } }

95 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

12. Annotations and interceptors


Annotations
If you've made it this far into this series of articles, you've already seen lots of annotations. Annotations are so important in Ceylon that it's extremely difficult to write any code without using them. But we have not yet really explored what an annotation is. Let's finally rectify that. The answer is simple: an annotation is a toplevel method that returns a subtype ofConstrainedAnnotation. Here's the definition of a some of our old friends:
shared Deprecated deprecated() { return Deprecated(); } shared Description doc(String description) { return Description(description.normalize()); } shared Authors by(String... authors) { return Authors( from (authors) select (String name) (name.normalize()) ); }

(Note that the third example uses the syntax introduced in this blog entry.) Of course, we can define our own annotations. (That's the whole point!)
shared Scope scope(Scope s) { return s; } shared Todo todo(String text) { return Todo(text); }

Since annotations are methods, annotation names always begin with a lowercase letter.

Annotation arguments
When we specify an annotation with a non-empty parameter list at a program element, we need to specify arguments for the parameters of the annotation. Just like with a normal method invocation, we have the choice between a positional argument list or a named argument list. We could write:
doc ("The Hello World program")

or:
96 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

doc { description="The Hello World program"; }

Likewise, we could write:


by ("Gavin", "Stephane", "Emmanuel")

or:
by { "Gavin", "Stephane", "Emmanuel" }

But with annotations whose arguments are all literal values, we have a third option. We can completely eliminate the punctuation, and just list the literal values.
doc "The Hello World program" by "Gavin" "Stephane" "Emmanuel"

As a special case of this, if the annotation has no arguments, we can just write the annotation name and leave it at that. We do this all the time with annotations like shared, formal, default, actual, abstract, deprecated, and variable.

Annotation types
The return type of an annotation is called the annotation type. Multiple methods may produce the same annotation type. An annotation type must be a subtype of ConstrainedAnnotation:
doc "An annotation. This interface encodes constraints upon the annotation in its type arguments." shared interface ConstrainedAnnotation<out Value, out Values, in ProgramElement> of OptionalAnnotation<Value,ProgramElement> | SequencedAnnotation<Value,ProgramElement> satisfies Annotation<Value> given Value satisfies Annotation<Value> given ProgramElement satisfies Annotated { shared Boolean occurs(Annotated programElement) { return programElement is ProgramElement; } }

97 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

The type arguments of this interface express constraints upon how annotations which return the annotation type occur. The first type parameter, Value, is simply the annotation type itself.

Annotation constraints
The second type parameter, Values, governs how many different annotations of given program element may return the annotation type. Notice that ConstrainedAnnotation has an of clause telling us that there are only two direct subtypes. So any annotation type must be a subtype of one of these two interfaces:

If an annotation type is a suptype of OptionalAnnotation, at most one annotation of a given program element may be of this annotation type, or, otherwise if an annotation type is a suptype of SequencedAnnotation, more than one annotation of a given program element may be of this annotation type.
doc "An annotation that may occur at most once at a single program element." shared interface OptionalAnnotation<out Value, in ProgramElement> satisfies ConstrainedAnnotation<Value,Value?,ProgramElement> given Value satisfies Annotation<Value> given ProgramElement satisfies Annotated {} doc "An annotation that may occur multiple times at a single program element." shared interface SequencedAnnotation<out Value, in ProgramElement> satisfies ConstrainedAnnotation<Value,Value[],ProgramElement> given Value satisfies Annotation<Value> given ProgramElement satisfies Annotated {}

Finally, the third type parameter, ProgramElement, of ConstrainedAnnotation constrains the kinds of program elements at which the annotation can occur. The argument to ProgramElement must be a metamodel type. So the argument Type<Number>would constrain the annotation to occur only at program elements that declare a subtype of Number. The argumentAttribute<Bottom,String> would constrain the annotation to occur only at program elements that declare an attribute of typeString. Here's a couple of examples I copied and pasted straight from the language spec:
shared interface Scope of request | session | application
98 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

satisfies OptionalAnnotation<Scope,Type<Object>> {} shared class Todo(String text) satisfies OptionalAnnotation<Todo,Annotated> { shared actual String string = text; }

Reading annotation values at runtime


Annotation values may be obtained by calling the toplevel method annotations() defined in the language module.
shared Values annotations<Value,Values,ProgramElement>( Type<ConstrainedAnnotation<Value,Values,ProgramElement>> annotationType, ProgramElement programElement) given Value satisfies ConstrainedAnnotation<Value,Values,ProgramElement> given ProgramElement satisfies Annotated { ... }

So to obtain the value of the

doc

annotation of the

Person

class, we write:

String? description = annotations(Description, Person)?.description;

Note that the expression Person returns the metamodel object for the class Person, an instance of ConcreteClass<Person>. To determine if the method write:
stop()

of a class named

Thread

is deprecated, we can

Boolean deprecated = annotations(Deprecated, Thread.stop) exists;

Note that the expression Thread.stop returns the metamodel object for the method stop() of Thread, an instance ofMethod<Thread,Void>. Here's two more examples, to make sure you get the idea:
Scope scope = annotations(Scope, Person) ? request; Todo[] todos = annotations(Todo, method);

Yeah, everything's set up so that annotations() returns Scope? for the optional annotation type Scope, and Todo[] for the sequenced annotation type Todo. Nice, huh? Of course, it's much more common to work with annotations in generic code, so you're more likely to be writing code like this:
99 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Entry<Attribute<Bottom,Object?>,String>[] attributeColumnNames(Class<Object> clazz) { return from (clazz.members(Attribute<Bottom,Object?>)) select (Attribute<Bottom,Object?> att) (att->columnName(att)); } String columnName(Attribute<Bottom,Object?> member) { return annotations(Column, member)?.name ? member.name; }

As you can see, Ceylon annotations are framework-developer-heaven.

Defining annotations
We've seen plenty of examples of annotations built into Ceylon. Application developers don't often define their own annotations, but framework developers do this all the time. Let's see how we could define an annotation for declarative transaction management in Ceylon.
Transactional transactional(Boolean requiresNew = false) { return Transactional(requiresNew); }

This method simply produces an instance of the class Transactional that will be attached to the metamodel of an annotated method or attribute. The metaannotation specifies that the annotation may be applied to methods and attributes, and may occur at most once on any member.
shared class Transactional(Boolean requiresNew) satisfies OptionalAnnotation<Transactional,Member<Bottom,Void>> { shared Boolean requiresNew = requiresNew; }

Now we can apply our annotation to a method of any class.


shared class OrderManager() { shared transactional void createOrder(Order order) { ... } ... }

We could specify an explicit argument to the parameter of positional argument list:


shared transactional (true) void createOrder(Order order) { ... }

transactional

using a

100 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Alternatively, we could use a named argument list:


shared transactional { requiresNew=true; } void createOrder(Order order) { ... }

We won't need to use reflection in our example, since Ceylon's module architecture includes special built-in support for using annotations to add interceptors to methods and attributes.

Interceptors
An interceptor allows frameworks to react to events like method invocations, class instantiations, or attribute evaluations. We don't need to write any special annotation scanning code to make use of interceptors. Ceylon handles this for us at class-loading time. All we need to do is have our Transactional class implement the interfaces MethodAnnotation and AttributeAnnotation:
shared class Transactional(Boolean requiresNew) satisfies OptionalAnnotation<Transactional,Member<Bottom,Void>> & MethodAnnotation & AttributeAnnotation {

shared Boolean requiresNew = requiresNew;

doc "This method is called whenever Ceylon loads a class with a method annotated |transactional|. It registers a transaction management interceptor for the method." shared actual void onDefineMethod<Instance,Result,Argument...>(OpenMethod<Instance,R esult,Argument...> method) { method.intercept() onInvoke(Instance instance, Result proceed(Argument... args), Argument... args) { if (currentTransaction.inProcess || !requiresNew) { return proceed(args); } else { currentTransaction.begin(); try { Result result = proceed(args); currentTransaction.commit(); return result; } catch (Exception e) { currentTransaction.rollback();
101 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

throw e; } } } }

doc "This method is called whenever Ceylon loads a class with an attribute annotated |transactional|. It registers a transaction management interceptor for the attribute." shared actual void onDefineAttribute<Instance,Result>(OpenAttribute<Instance,Result> attribute) { attribute.intercept() onGet(Instance instance, Result proceed()) { if (currentTransaction.inProcess || !requiresNew) { return proceed(); } else { currentTransaction.begin(); try { Result result = proceed(); currentTransaction.commit(); return result; } catch (Exception e) { currentTransaction.rollback(); throw e; } } } } }

The intercept() method registers the interceptor - a kind of callback method. Again, we're using the syntax discussed here.

Conclusion
I think this is probably a good place to end this series for now, since I've covered all of the bits of Ceylon that have been properly worked out at this stage. At some stage I'll go back and update / clean up these posts and perhaps even reorganize the material a bit. Thanks for following along, and please keep letting us know your feedback!

102 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

13. Resources
http://blog.talawah.net/2011/04/gavin-king-unviels-red-hats-top-secret.html. http://www.infoq.com/news/2011/04/ceylon. http://relation.to/tag/Ceylon. http://relation.to/Tutorials/IntroductionToCeylon. http://en.wikipedia.org/wiki/Ceylon_%28disambiguation%29.

103 | P a g e Source: http://relation.to/tag/Introduction+to+Ceylon

Das könnte Ihnen auch gefallen