Tour of Ceylon: Basics

Let's start!

Writing a simple program

Here's a classic example program.

void hello() {
    print("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();

Or you can run it directly from the command line.

Ceylon doesn't have Java-style static methods, but you can think of toplevel methods as filling the same role. 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.

Running the program from the command line

Let's try it out. Save the above code in the file ./source/hello.ceylon and then run the following commands:

ceylon-0.1/bin/ceylonc source/hello.ceylon
ceylon-0.1/bin/ceylon -run hello default

where ceylon-0.1 is the path to your Ceylon install directory. You should see the message Hello, World!. You will find the compiled module archive default.car in the directory ./modules/default.

If you're having trouble getting started with the command line tools, the command line distribution of Ceylon contains a file named README.md in the root directory that contains instructions on compiling and running the simple examples in the samples/ directory.

A very useful trick is:

ceylon-0.1/bin/ceylonc -help
ceylon-0.1/bin/ceylon -help

Running the program from the IDE

To run the program in Ceylon IDE, go to the Ceylon perspective, create a new project using File > New > Ceylon Project, then create a new .ceylon file using File > New > Ceylon Unit. Put the code for hello() in this new file, then select the file and run it using Run > Run As > Ceylon Application.

Or, if you're unfamiliar with Eclipse, go to Help > Cheat Sheets, open the Ceylon item, and run the Hello World with Ceylon cheat sheet which takes you step by step through the process.

String literals

String literals in Ceylon may span multiple lines. Try this:

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

The output is:

Hello, 
World!

Note that because the second line of the string literal contained whitespace right up until the first character of the first line of the string literal, all that whitespace was automatically removed. This helps us format our code nicely.

It's often useful to collapse whitespace in a multiline string literal. The String class has an attribute called normalized. We can use it like this:

void hello() {
    value message = "Hello, 
                     World!";
    print(message.normalized);
}

Which results in the output:

Hello, World!

Multiline strings are especially useful for adding documentation to a program.

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() {
    print("Hello, World!");
}

Or like this:

//The classic Hello World program
void hello() {
    print("Hello, World!");
}

But it's much better to use the doc annotation for comments that describe declarations.

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

The doc, by, see and throws annotations contain documentation that is included in the output of the Ceylon documentation compiler, ceylond.

Notice that when an annotation argument is a literal, it doesn't need to be enclosed in parentheses. We can write simply:

by "Gavin"

instead of:

by ("Gavin")

Annotations like doc, by, see, and throws, aren't keywords. They're just ordinary identifiers. The same is true for annotations which are part of the language definition, for example: abstract, variable, shared, formal, default, actual, etc. This is quite different to other C-like languages. On the other hand, void is a keyword, just like in C or Java.

Formatting inline documentation

The doc annotation may contain Markdown formatting.

doc "The classic [Hello World program][helloworld]
     that prints a message to the console, this 
     time written in [Ceylon][]. 

     This simple program demonstrates:

     1. how to define a toplevel method, and
     2. how to `print()` a literal `String`.

     You can compile and run `hello()` from the 
     command line like this:

         ceylonc source/hello.ceylon
         ceylon -run hello default

     Or you can use `Run As > Ceylon Application` 
     in the IDE.

     [helloworld]: http://en.wikipedia.org/wiki/Hello_world_program
     [Ceylon]: http://ceylon-lang.org"

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

Since Markdown is sensitive to the initial column in which text appears, you need to be careful to indent the lines of the multiline string literal correctly, as we've done here.

String interpolation and concatenation

Let's make our program to tell us a little more about itself.

void hello2() {
    print("Hello, you ran me at " 
           process.milliseconds
          " ms, with " 
           process.arguments.size
          " command line arguments.");
}

Notice how our message contains interpolated expressions. This is called a string template. A string template must begin and end in a string literal. The following is not legal syntax:

print("Hello, you ran me at " 
       process.milliseconds); //compile error!

But we can easily fix it:

print("Hello, you ran me at " 
       process.milliseconds 
      "");

(If you're wondering why the syntax isn't something like "Hello, you ran me at ${process.milliseconds}", here's why.)

The + operator you're probably used to is an alternative way to concatenate strings, and more flexible in many cases:

print("Hello, you ran me at " + 
       process.milliseconds.string +
      " ms, with " +
       process.arguments.size.string +
      " command line arguments.");

Note that when we use + to concatenate strings, we have to explicitly invoke the string attribute to convert numeric expressions to strings. The + operator does not automatically convert its operands to strings, so the following does not compile:

print("Hello, you ran me at " + 
       process.milliseconds +
      " ms, with " +
       process.arguments.size +
      " command line arguments.");    //compile error!

Dealing with objects that aren't there

Let's take 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#.

Let's consider an overly-verbose example to start with (we'll get on to the preferred form in a moment):

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

The type String? indicates that name may contain a null value. We then use the if (exists ...) control structure to split the code that deals with a non-null vs a null name.

Unlike Java, locals, parameters, and attributes that may contain null values must be explicitly declared as being of optional type (the T? syntax). There's simply no way to assign null to a local that isn't of optional type. The compiler won't let you.

Nor will the Ceylon compiler let you do anything dangerous with a value of type T? - that is, anything that could cause a NullPointerException in Java - without first checking that the value is not null using if (exists ... ).

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.

Note that the syntax String? is just an abbreviation for the union type Nothing|String. The value null isn't a primitive value in Ceylon, it's just a perfectly ordinary instance of the perfectly ordinary class Nothing.

It's possible to declare the local name inside the if (exists ... ) condition (and because Ceylon has type inference, you don't even have to declare the type):

String greeting;
if (exists name = process.arguments.first) {
    greeting = "Hello, " name "!";
}
else {
    greeting = "Hello, World!";
}
print(greeting);

This is the preferred style most of the time, since we can't actually use name for anything useful outside of the if (exists ... ) construct.

Operators for handling null values

There are a couple of operators that will make your life easier when dealing with null values.

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

The ? operator returns its first argument if the first argument is not null, or its second argument otherwise. It's a more convenient way to handle null values in simple cases.

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

Defaulted parameters

While we're on the topic of values that aren't there, it's worth mentioning that a method parameter may specify a default value.

void hello(String name="World") {
    print("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 an ellipsis (i.e. String...). But we'll come back to them after we discuss sequences and for loops.

There's more...

Let's now discuss classes, interfaces, and objects.