Help

This is the first installment in a series of articles introducing the Ceylon language. Note that some features of the language may change before the final release.

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.

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

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.

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 doc annotation for comments that describe declarations.

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.

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. The + operator immediately evaluates its operand expressions and produces an immutable String object. 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 optional type. And unlike other languages with typesafe null value handling, an optional type in Ceylon is not an algebraic datatype that wraps the definite value. Instead, Ceylon uses an ad-hoc union 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 assign null 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 a NullPointerException 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 name inside the if (exists ... ) condition:

String greeting; 
if (exists String name = process.arguments.first) {
    greeting = "Hello, " name "!";
} 
else {
    greeting = "Hello, World!"; 
}
writeLine(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

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.

There's more...

In Part 2, we'll see how to define our own types: classes, interfaces, and objects.

20 comments:
 
27. Apr 2011, 23:14 CET | Link

Gavin,

first of all I'd like to express that I really like what I've seen from Ceylon so far. I think building a modern type-safe language with the Java spirit in mind is the right way to go.

Some general questions:

  • Who is the team behind Ceylon? Are there other folks beside you known to the community? I think knowing who is driving this effort will help in building trust by potential users.
  • Will there be field literals in Ceylon? I think they would be of great help in certain areas of meta programming.
  • Will there be some sort of DI mechanism built into Ceylon or will this be left for frameworks/APIs such as CDI etc.?
  • At some other place there was the question why you didn't chose Xtext and friends as basis for Ceylon. Could you comment on that? I don't know whether Xtext's abilities would be sufficient for implementing a language like Ceylon but its advantages like editor generation seem promising.

And two questions related to the code:

  • Related to the extended Hello World example: where does the process object come from? It seems to be some sort of static object, meaning it could be accessed from everywhere. Is this really better than Java's main method which injects the program arguments into one specific place?
  • Will the Deprecated annotation have a mandatory replacedBy attribute? There are so many deprecated methods out there which don't make clear what to use instead, so I'd really love to see such a feature :)

Thanks, Gunnar

ReplyQuote
 
27. Apr 2011, 23:48 CET | Link
Who is the team behind Ceylon? Are there other folks beside you known to the community? I think knowing who is driving this effort will help in building trust by potential users.

I was hoping some folks would speak up for themselves, but they havn't yet. :-) Our group includes folks from this blog (Emmanuel, Ales, Jason, Max, Pete), a couple of folks from the OpenJDK team (Andrew Haley and Gary Benson), and a couple of other folks from around Red Hat (Andrew Dinn, David LLoyd). And my boss, Mark Little, is the guy who has put a whole lot of work into getting support from the organization we work for.

Of course, until now I'm the only guy working fulltime on this. But that will change soon.

Will there be field literals in Ceylon? I think they would be of great help in certain areas of meta programming.

Yes. All named program elements have a typesafe metamodel. This is one of the best features of the language. But we're a fair way off actually implementing this in the compiler!

Will there be some sort of DI mechanism built into Ceylon or will this be left for frameworks/APIs such as CDI etc.?

Not DI as such. But I'm looking closely at ideas like ML modules and, more likely, the notion of making a package into a kind of object that can be extended and have its members overrided. IMO, DI is more or less a workaround for the problem that instantiation is not a polymorphic operation in most OO languages. Now, in Ceylon, instantiation of member classes (inner classes in Java terminology) actually is a polymorphic operation. So if you write all your classes as members of some outer object, you will be able to override them by subclassing that object. But that's not a solution for toplevel declarations. For toplevel declarations you would need to be able to have abstract packages and subclass a package.

I'm not going to try to solve this problem in version 1.0 of the language, but it is on the cards for some future version of the language.

At some other place there was the question why you didn't chose Xtext and friends as basis for Ceylon. Could you comment on that? I don't know whether Xtext's abilities would be sufficient for implementing a language like Ceylon but its advantages like editor generation seem promising.

To be honest I had never really looked at Xtext. I'm quite skeptical that it's really going to give you a full-featured editor like the Eclipse Java editor for free, and I should also point out that some of the parsing problems in Ceylon (especially the annotation syntax) are quite advanced and absorbed months of effort pushing ANTLR to its limits. Perhaps Xtext truly is amazing, but I don't know of any other general purpose language which has been implemented this way.

Note that the parsing / syntax tree generation side of the project is already done. And I've made really great progress on the typechecker. It's the bytecode generation side which is lagging behind due to unforseen scheduling problems.

Related to the extended "Hello World" example: where does the "process" object come from? It seems to be some sort of static object, meaning it could be accessed from everywhere. Is this really better than Java's main method which injects the program arguments into one specific place?

It's a toplevel attribute. Actually, if you really must know, here's it's declaration in the language module.

shared object process {

    shared String[] arguments { throw; }
    
    shared Entries<String, String> switches { throw; }

    shared Entries<String,String> properties { throw; }

    shared void write(String string) { throw; }
    
    shared void writeLine(String line) { write(line); write("\n"); }
    
    shared String readLine() { throw; }

}

Yes, well, there's ... ahem ... some implementation details to be filled in there! :-/

P.S. The object keyword just introduces an attribute/local and an anonymous class in one step. Quite useful.

Will the Deprecated annotation have a mandatory "replacedBy" attribute? There are so many deprecated methods out there which don't make clear what to use instead, so I'd really love to see such a feature :)

Hehe, I don't think it will be mandatory :-) Actually I would be inclined to think that see already serves the purpose of replacedBy.

 
27. Apr 2011, 23:51 CET | Link
Is this really better than Java's main method which injects the program arguments into one specific place?

Not much better, but it does mean that arguments are accessible from everywhere, not only from the entry point. It's not quite clear to me why they aren't available via the Java System class.

 
28. Apr 2011, 05:45 CET | Link
Charles Crouch
Is the plan for Ceylon to play nicely with the existing Java library eco-system? e.g. will I be able to use Hibernate to persist a properly annotated Ceylon object?

Cheers
Charles
 
28. Apr 2011, 05:56 CET | Link
Is the plan for Ceylon to play nicely with the existing Java library eco-system? e.g. will I be able to use Hibernate to persist a properly annotated Ceylon object?

There will be interoperability, certainly. But it's a non-goal to be able to use existing Java frameworks from Ceylon. Ceylon will enable so much more elegant frameworks that using Java frameworks with it would just be silly.

 
28. Apr 2011, 09:32 CET | Link
Charles Crouch
As long as "interoperability" includes being able to call regular java class libraries. Also I guess it makes sense that new languages require/enable new frameworks e.g. Groovy and Grails is presumably more popular than Groovy and Seam? Its just that, if there will be no integration with existing frameworks, people will need to wait for their replacements to be coded too. Perhaps you are working on a Ceylon web/persistence framework as well :-)!
 
28. Apr 2011, 13:41 CET | Link

I like the project name too, It's my country name Sri Lanka (ceylon), Thank's for using it to spread the name.

 
28. Apr 2011, 13:51 CET | Link
Aliaksei Lahachou | aliaksei.lahachou(AT)gmail.com

Sorry if it was already answered and I missed it somewhere. Will we see the null-coalescing operator (?? operator) from C# in Ceylon? I find it very neat.

int? x = null;
// y = x, unless x is null, in which case y = -1.
int y = x ?? -1;
 
28. Apr 2011, 14:58 CET | Link
Aliaksei Lahachou | aliaksei.lahachou(AT)gmail.com

Never mind. Found it in pt. 2 :)

 
28. Apr 2011, 15:42 CET | Link
wujek

Hi Gavin. Just one question: why a separate type Void and a keyword void? What is the difference between:

void someFun() and Void someFun() ?

For other types, like String (actually I don't know if Ceylon will have this type), you simply have: String someFun() and not some keyword. Seems inconsistent. Could you please shed some light?

 
28. Apr 2011, 16:53 CET | Link
As long as "interoperability" includes being able to call regular java class libraries.

Yes, of course. But there are going to be some problems in the hopefully rare cases where the Java library uses wildcard types or overloading. Ceylon cannot represent wildcard types within its type system. Overloading is probably a bit easier to deal with, but there's still going to be some tough cases.

Also I guess it makes sense that new languages require/enable new frameworks e.g. Groovy and Grails is presumably more popular than Groovy and Seam? Its just that, if there will be no integration with existing frameworks, people will need to wait for their replacements to be coded too. Perhaps you are working on a Ceylon web/persistence framework as well :-)!

Remember that, right from the beginning, we have the explicit goal of a new SDK (by which we really mean a bunch of modules we're going to write). These will definitely include modules for working with HTTP and HTML, and some kind of wrapper for JPA. But of course I haven't figured out many details of this yet.

 
28. Apr 2011, 16:59 CET | Link

Hi Gavin. Just one question: why a separate type Void and a keyword void?

Here's the difference:

void noop() {
}

Void noop() {
    return null; //or anything you like
}

The void keyword tells the compiler that no return value is needed when it does definite return checking.

Now, of course, we probably could have made the compiler smart enough to look at the declared return type and reason that if the return type is ceylon.language.Void then no definite return is required. But that would mean delaying definite return checking (which is more or less a purely syntactic check) until after type analysis completes (because you can't be sure that Void doesn't refer to foo.bar.baz.Void).

Also, void is traditional and non-harmful ;-)

 
28. Apr 2011, 17:01 CET | Link
For other types, like String (actually I don't know if Ceylon will have this type)

For now Ceylon has String, and I think there's some really good reasons to keep it. But it's true that I have occasionally found the idea of defining String to be an alias of Character[] to be seductive ;-)

 
16. May 2011, 20:19 CET | Link
Arbi Sookazian

Good (and exciting) news! I have posted a discussion in the JVM languages group on linkedin.com: Ceylon

 
25. May 2011, 23:41 CET | Link
David Hardin

In an example you give:

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

I was surprised to see that name was declared as String name, and not String? name. Forgive me if this explained later on.

 
26. May 2011, 05:11 CET | Link
I was surprised to see that name was declared as String name, and not String? name. Forgive me if this explained later on.

In this variation we're defining a local inside the if (exists ...) construct, that is visible only inside the if block, where it has type String. In the previous case, the local was declared and visible outside of the if (exists ...) construct, where it has type String?.

 
07. Jun 2011, 15:19 CET | Link
Leo

the doc looks like very miscellaneous.

 
19. Aug 2011, 13:55 CET | Link
Ceylon doesn't have Java-style static methods

What about static fields? Where is a place to hold global constant of an entire project?

 
13. Sep 2014, 12:44 CET | Link

Aside from just letting him chew on the links, I also used a chain of them to hook up toys to the stroller bars, so he could freely play with them but they won't drop to the floor. Just remember to buy spare filters, light bulbs and the powercable.

Post Comment