Note: information on this page refers to Ceylon 1.1, not to the current release.
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:
print("Hello, World!");
String literals in Ceylon may span multiple lines. Try this:
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:
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 */
shared void hello() {
print("Hello, World!");
}
Or like this:
//The classic Hello World program
shared 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 ("Trompon the Elephant")
see (`function goodbye`)
throws (`class IOException`)
shared 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 ("Trompon the Elephant")
see (`function goodbye`)
throws (`class IOException`)
shared 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"
shared 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.
print("Hello, this is Ceylon ``language.version``
running on ``runtime.name`` ``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 jvm 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 " + runtime.name + " " + runtime.version + "!\n" +
"You ran me at " + system.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 " + runtime.name + " " + runtime.version + "!\n" +
"You ran me at " + system.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.)
This doesn't compile:
String name = process.arguments.first; //compile error: String? is not assignable to String
String greeting = "Hello, ``name``!";
print(greeting);
Instead, we have to explicitly accommodate the possibility that there are no arguments:
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 reference a null
value. We
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 else
s:
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";
Finally, the ?.
operator lets us call operations on optional types:
Integer length = name?.size else 0;
If name
is null, name?.size
evaluates to null
. Otherwise, the
size
attribute of String
is evaluated.
If we need to squeeze a whole chain of then
s/else
s 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 original example to something
reasonable:
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 = "Adieu"; //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 an explicit list of enumerated arguments,
- pass an 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. So it's 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 AssertionError`, "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.