Tour of Ceylon: Basics

Let's get started!

Before we can get into any of the really interesting and powerful features of this language, we need to get comfortable with some of the basic syntax, so we'll know what we're looking at later on, when we get up to the really good stuff.

String literals

As we just saw, a string literal is text enclosed in double-quotes:

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

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 functions 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 (`function goodbye`)
throws (`class IOException`)
void hello() {
    print("Hello, World!");
}

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

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.)

Since the doc annotation is ubiquitous, its name and parentheses may be left out whenever it occurs as the first annotation in the list of annotations of a program element:

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

Formatting inline documentation

The doc annotation may contain Markdown formatting.

"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 function, and
 2. how to `print()` a literal `String`.

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

     ceylon compile source/hello.ceylon
     ceylon run -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.

Escape sequences

Inside a string literal, you can use the escape sequences \n, \t, \\, \" and friends that you're used to from other C-like languages.

print("\"Hello!\", said the program.");

You can also use 2-byte and 4-byte hexadecimal escape sequences to embed Unicode characters in your text.

"The mathematical constant \{#03C0}, the
 ratio of the circumference of a circle
 to its diameter."
Float pi=calculatePi();

"The mathematical constant \{#0001D452},
 the base of the natural logarithm."
Float e=calculateE();

Even better, you can identity a Unicode character by its name.

"The mathematical constant \{GREEK SMALL LETTER PI}, the 
 ratio of the circumference of a circle to its diameter."
Float pi=calculatePi();

"The mathematical constant \{MATHEMATICAL ITALIC SMALL E},
 the base of the natural logarithm."
Float e=calculateE();

Ceylon strings are composed of UTF-32 characters, as we'll see later in the tour.

Verbatim strings

Sometimes, escape sequence interpolation is annoying, for example, when embedding code in a string literal. If we use three double-quotes, """, to delimit our string, we get a verbatim string, which may contain unescaped backslash and double-quote characters:

print(""""Hello!", said the program.""");

String interpolation and concatenation

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

"The Hello World program ... version 1.1!"
void hello() {
    print("Hello, this is Ceylon ``language.version``  
           running on Java ``runtime.version``!\n
           You ran me at ``system.milliseconds`` ms, 
           with ``process.arguments.size`` arguments.");
}

Notice how our message contains interpolated expressions, delimited using "doublebacks", that is, two backticks. This is called a string template.

On my machine, this program results in the following output:

Hello, this is Ceylon 1.0.0
running on Java 1.7!

You ran me at 1362763185067 ms,
with 0 arguments.

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

print("Hello, this is Ceylon " + language.version +  
      "running on Java " + process.vmVersion + "!\n" +
      "You ran me at " + process.milliseconds.string + 
      " ms, with " + process.arguments.size.string + 
      " 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, this is Ceylon " + language.version +  
      "running on Java " + process.vmVersion + "!\n" +
      "You ran me at " + process.milliseconds +  //compile error!
      " ms, with " + process.arguments.size +    //compile error!
      " arguments.");

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 work our way up to a more convenient form.)

"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 handle the case of a null name separately from the case of a non-null name.

It's possible to abbreviate the code we just saw by declaring the local name inside the if (exists ... ) condition:

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. (But this still isn't the most compact way to write this code.)

Optional types

Local variables, 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. This is an error:

String name = null; //compile error: null is not an instance of String

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 ... ). The following is also an error:

String? name = process.arguments.first;
print("Hello " + name + "!"); //compile error: name is not Summable

In fact, it's not even possible to use the equality operator == with an expression of optional type. We can't write:

String? name = process.arguments.first;
if (name==null) { ... } //compile error: name is not Object

like we 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.

In a language with static typing, we're always wanting to know what the type of something is. So what's the type of null?

That's easy to answer: null is a Null.

Yes, that's right: the value null isn't a primitive value in Ceylon, it's just a perfectly ordinary instance of the perfectly ordinary class Null, at least from the point of view of Ceylon's type system.

And the syntax String? is just an abbreviation for the union type Null|String.

That's why we can't call operations of String on a String?. It's simply a different type! The if (exists ...) construct narrowed the type of name inside the if block, allowing us to treat name as a String there.

(As an aside, if you're concerned about performance, it's worth mentioning that the Ceylon compiler does some special magic to transform this value to a virtual machine-level null, all under the covers.)

Operators for handling null values

There are a couple of operators that will make your life easier when dealing with null values. The first is else:

String greeting = "Hello, " + (name else "World");

The else operator produces:

  • its first operand if the first operand is not null, or
  • its second operand otherwise.

It's a more convenient way to handle null values in simple cases. You can chain multiple elses:

String name = firstName else userId else "Guest";

There's also an operator for producing a null value:

String? name = !arg.trimmed.empty then arg;

The then operator produces

  • its second operand if its first operand evaluates to true, or
  • null otherwise.

You can chain an else after a then to reproduce the behavior of C's ternary ?: operator:

String name = !arg.trimmed.empty then arg else "World";

If we need to squeeze a whole chain of thens/elses into a single expression, we can use the "poorman's switch" idiom:

String sign = (int>1P then "enormous")
         else (int<0 then "negative")
         else (int>0 then "positive")
         else "zero";

Using else, we can finally simplify our example to something reasonable:

"Print a personalized greeting"
void hello() {
    print("Hello, ``process.arguments.first else "World"``!");
}

Yes, after all that, it's a one-liner ;-)

Functions and values

The two most basic constructs found in almost every programming language are functions and variables. In Ceylon, "variables" are, by default, assignable exactly once. That is, they can't be assigned a new value after an initial value has been assigned. Therefore, we use the word value to talk about "variables" collectively, and reserve the word variable to mean a value which is explicitly defined to be reassignable.

String bye = "Adios";        //a value
variable Integer count = 0;  //a variable

bye = "Adeu";  //compile error
count = 1;     //allowed

Note that even a value which isn't a variable in this sense, may still be "variable" in the sense that its value varies between different runs of the program, or between contexts within a single execution of the program.

A value may even be recalculated every time it is evaluated.

String name { return firstName + " " + lastName; } 

If the values of firstName and lastName vary, then the value of name also varies between evaluations.

A function takes this idea one step further. The value of a function depends not only upon the context in which it is evaluated, but also upon the arguments to its parameters.

Float sqr(Float x) { return x*x; }

In Ceylon, a value or function declaration can occur almost anywhere:

  • as a toplevel, belonging directly to a package,
  • as an attribute or method of a class, or
  • as a block-local declaration inside a different value or function body.

Indeed, as we'll see later, a value or function declaration may even occur inside an expression in some cases.

Functions declarations look pretty similar to what you're probably already used to from other C-like languages, with two exceptions. Ceylon has:

  • defaulted parameters, and
  • variadic parameters.

Defaulted parameters

A function 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 function:

hello(); //Hello, World!
hello("JBoss"); //Hello, JBoss!

Defaulted parameters must be declared after all required parameters in the parameter list of a function.

Variadic parameters

A variadic parameter of a function or class is declared using a postfix asterisk, for example, String*. There may be only one variadic parameter for a function or class, and it must be the last parameter.

void helloEveryone(String* names) { 
    // ... 
}

Inside the function body, the parameter names has type [String*], a sequence type, which we'll learn about later. Thus, we can iterate the parameter using a for loop to get at the individual arguments.

void helloEveryone(String* names) {
    for (name in names) {
        hello(name);
    }
}

A nonempty variadic parameter is declared using a postfix plus sign, for example, String+. In this case, the caller must supply at least one argument.

void helloEveryone(String+ names) {
    for (name in names) {
        hello(name);
    }
}

To pass an argument to a variadic parameter we have three choices. We could:

  • provide a an explicit list of enumerated arguments,
  • pass in iterable object producing the arguments, or
  • specify a comprehension.

The first case is easy:

helloEveryone("world", "mars", "saturn");

For the second case, Ceylon requires us to use the spread operator:

String[] everyone = ["world", "mars", "saturn"];
helloEveryone(*everyone);

We'll come back to the third case, comprehensions, later in the tour.

Fat arrows and forward declaration

Ceylon's expression syntax is much more powerful than Java's, and it's therefore possible to express a lot more in a single compact expression. It's therefore extremely common to encounter functions and values which simply evaluate and return an expression. So Ceylon lets us abbreviate such function and value definitions using a "fat arrow", =>. For example:

String name => firstName + " " + lastName;

Or:

Float sqr(Float x) => x*x;

Now's the time to get comfortable with this syntax, because you're going to be seeing quite a lot of it. Take careful note of the difference between a fat arrow:

String name => firstName + " " + lastName;

And an assignment:

String name = firstName + " " + lastName;

In the first example, the expression is recomputed every time name is evaluated. In the second example, the expression is computed once and the result assigned to name.

We're even allowed to define a void function using a fat arrow. Earlier, we could have written hello() like this:

void hello() => print("Hello, World!");

In Java and C#, we're allowed to separate the declaration of a variable from the initialization of its value. We've already seen that this is also allowed in Ceylon. So we can write:

String name;
name = firstName + " " + lastName;
print(name);

But Ceylon even lets us do this with fat arrows:

String name;
name => firstName + " " + lastName;
print(name);

And even functions:

Float sqr(Float x);
sqr(Float x) => x*x;
print(sqr(0.01));

void hello();
hello() => print("Hello, World!");
hello();

The compiler makes sure we don't evaluate a value or invoke a function before assigning it a value or specifying its implementation, as we'll see later. (Because if we did, it would result in a NullPointerException, which Ceylon doesn't have!)

Numbers

Unfortunately, not every program is as simple and elegant as "hello world". In business or scientific computing, we often encounter programs that do fiendishly complicated stuff with numbers. Ceylon doesn't have any primitive types, so numeric values are usually represented by the classes Integer and Float, which we'll come back to later in the tour.

Float literals are written with a decimal point, and Integer literals without:

Integer one = 1;
Float zero = 0.0;

Even though they're classes, you can use all the usual numeric literals and operators with them. For example, the following function efficiently determines if an Integer represents a prime number:

"Determine if `n` is a prime number."
throws (`class AssertionException`, "if `n<2`")
Boolean prime(Integer n) {
    "`n` must be greater than 1"
    assert (n>1);
    if (n<=3) {
        return true;
    }
    else if (n%2==0 || n%3==0) {
        return false;
    }
    else if (n<25) {
        return true;
    }
    else {
        for (b in 1..((n.float^0.5+1)/6).integer) {
            if (n%(6*b-1)==0 || n%(6*b+1)==0) {
                return false;
            }
        }
        else {
            return true;
        }
    }
}

Try it, by running the following function:

"Print a list of all two-digit prime numbers."
void findPrimes()
    => printAll { for (i in 2..99) if (prime(i)) i };

Heh, this was just a little teaser to keep you interested. We'll explain the syntax we're using here a bit later in the tour.

There's more...

Ceylon is an object-oriented language, so an awful lot of the code we write in Ceylon is contained in a class. Let's learn about classes right now, before we come back to more of the basic stuff.