Help

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

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 some shared declaration.

Here, count is a local variable of the initializer of Counter:

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

But in the following two examples, count is an attribute:

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

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

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.

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

OK, so here's the examples. The if/else statement is totally traditional:

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

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 or break statement. There's no C-style 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:

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 strings has type String[].

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

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 location of the source file in which it is declared. A class named Hello in the package org.jboss.hello must 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 write org.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.

There's more...

Now that we've mopped up a few missing topics, we're ready to look at first class functions in Part 8, and the declarative object-tree-builder syntax for defining user interfaces and structured data in Part 9.

If you're interested, the Ceylon module system is described briefly here.

28 comments:
 
30. Apr 2011, 12:42 CET | Link

Great stuff Gavin! Very exciting stuff. I love the simplicity and comprehensiveness of the syntactical choices you guys have made. I'm specifically enamored with the recursive block structure and usage of closures. I'm already thinking of ways to incorporate your ideas into my pet dynamic language Nexus (www.nexuslang.org). Another area of interest is the declarative syntax (akin to DSLs) I'd love to hear more on that topic, as well as the language's handling of serialization and concurrency. Will you be adopting an actor model?

ReplyQuote
 
30. Apr 2011, 15:35 CET | Link
Adam Campbell wrote on Apr 30, 2011 06:42:
Great stuff Gavin! Very exciting stuff. I love the simplicity and comprehensiveness of the syntactical choices you guys have made. I'm specifically enamored with the recursive block structure and usage of closures. I'm already thinking of ways to incorporate your ideas into my pet dynamic language Nexus (www.nexuslang.org).

Thanks!

Another area of interest is the declarative syntax (akin to DSLs) I'd love to hear more on that topic,

I'm getting to it, just not sure if I need to do higher-order functions first. Probably, since defining functions in named argument lists is so super-nice for UI event handling, table generation, etc.

as well as the language's handling of serialization and concurrency. Will you be adopting an actor model?

The language itself won't have any concurrency primitives at all. I think it's better to leave that stuff to libraries. The primitive concurrency module will probably just be a very thin adapter to java.util.concurrent. Other modules can layer an actor framework or whatever over that.

30. Apr 2011, 17:08 CET | Link
NNNN
Hi,
I think I read that there are not primitives and Arrays left in ceylon.
Will ceylon offer anything for scientific computing (number crunching type) or is this outside of its intended usage?

good luck, although I am in the scala boat for now (and hope their specialisation stuff (http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/scala/specialized.html) will once help me getting rid of the while loops I have to use now.


best,
ido
30. Apr 2011, 17:38 CET | Link
I think I read that there are not primitives and Arrays left in ceylon.

Under the covers, the compiler will erase numeric types and Boolean to Java primitives. What I've been presenting here is the Ceylon language. I have not been discussing how it actually gets mapped to Java bytecode. (For example, Nothing gets completely erased.)

Will ceylon offer anything for scientific computing (number crunching type) or is this outside of its intended usage?

My background is scientific computing, but the truth is I've been out of that field for a long time. I don't even know if people these days consider the JVM as having good enough performance for numerical stuff.

 
01. May 2011, 10:50 CET | Link

You mention how Ceylon will be more modular and do away with things like OSGi and Maven, but haven't yet described how. What did you mean by this?

 
01. May 2011, 12:50 CET | Link

I'm just noticing that Ceylon classes do not have classic constructors, just code at the beginning of the class closure that initializes the class. How do you expect people will handle the need for multiple constructors, maybe I missed this point earlier. I suppose this question falls into the category of Ceylon does not support method overloading. It would be interesting to see an example which would be handled with multiple constructors (in Java), and how it would be handled the Ceylon way. Does this lead to over complication in the initializer code. Having multiple constructors allows you to logically break up different paths for object construction. This is especially needed when an instantiating an object that based on another class.

 
01. May 2011, 15:56 CET | Link
import org.jboss.hello { local Hi = Hello, ... }

This is nice, and in the past couple of years, especially after using Eclipse EMF libraries a bit, I always thought that java is missing this feature. I liked to see a construct like:

import java.util.Date as UDate;
import java.sql.Date as SDate;

but why { local Hi = Hello } and not for example { Hello as Hi }? I know, it's your language :), but IMHO { Hello as Hi } looks better...

 
01. May 2011, 16:59 CET | Link
but why { local Hi = Hello } and not for example { Hello as Hi }? I know, it's your language :), but IMHO { Hello as Hi } looks better...

Well, regularity. Everywhere else we introduce I name in the language, the syntax is of form (keyword|Type) Identifier Definition.

To be honest, I'm not especially in love with the reuse of the keyword local here. But you really want to limit the number of keywords in the language, so that people can use them as identifiers. Especially don't want to step on a useful English word like as. Here's the full list of keywords so far:

import decorate 
class interface object given 
assign void subtype local 
of extends satisfies abstracts 
in out 
return break continue throw retry 
if else switch case for fail do while try catch finally 
this outer super 
is exists nonempty

We could probably live with { Hi = Hello }, but it's not totally visually obvious which is the identifier which is being defined.

Actually the declaration syntax is a whole lot more regular than just (keyword|Type) Identifier Definition. All declarations follow this general schema:

Annotation* (keyword | InferableType) (TypeName | MemberName) TypeParams? Params* 
CaseTypes? Metatypes? ExtendedType? SatisfiedTypes? 
TypeConstraints? 
(Definition | ";")
 
01. May 2011, 17:09 CET | Link
I'm just noticing that Ceylon classes do not have classic constructors, just code at the beginning of the class closure that initializes the class. How do you expect people will handle the need for multiple constructors, maybe I missed this point earlier. I suppose this question falls into the category of "Ceylon does not support method overloading". It would be interesting to see an example which would be handled with multiple constructors (in Java), and how it would be handled the Ceylon way. Does this lead to over complication in the initializer code. Having multiple constructors allows you to logically break up different paths for object construction. This is especially needed when an instantiating an object that based on another class.

I discussed it to some extent in Part 2, with some tricks for getting around the lack of overloading.

But what I should add here is that it's not an anti-pattern in Ceylon to create a subclass to specialize just the initialization logic. I know there's a somewhat irrational fear in some quarters about the perceived dangers of creating lots of subclasses. Viewed from a rational software-engineering perspective, I just don't buy into this fear. Anyway, a subclass that doesn't define any new members is just a function. :-)

 
01. May 2011, 17:16 CET | Link
Ricardo wrote on May 01, 2011 04:50:
You mention how Ceylon will be more modular and do away with things like OSGi and Maven, but haven't yet described how. What did you mean by this?

What I mean is that the notion of module - really a super-package, with it's own visibility scope, will be built into the whole architecture, right from the standard source directory layout that the compiler understands, to the standard module archive format, to the standard module repository layout. The whole toolchain will understand all this. The compiler won't produce .class files, it will produce module archives in a specified local or remote module repository. It will know how to fetch dependencies from local or remote module repositories. Etc.

And the modules will always be deployed to a module runtime (JBoss Modules) with a peer-to-peer classloader architecture. The module runtime will know how to search for and execute modules defined in standard source directories and standard module repositories.

Instead of a classpath, you just have a list of module repos that you want the compiler or runtime too look for modules in.

 
01. May 2011, 20:50 CET | Link
Georg Ragaller
The for loop has an optional fail block, which is executed when the loop completes normally

Why is that optional block a fail block, when it's executed if the loop completes normally? Seems to be a contradiction to use fail as keyword here. By the way: I like all the rest ...

 
01. May 2011, 23:23 CET | Link
Georg Ragaller wrote on May 01, 2011 14:50:
Why is that optional block a fail block, when it's executed if the loop completes normally? Seems to be a contradiction to use fail as keyword here. By the way: I like all the rest ...

I agree with Georg. fail seems off here. What about using default? Does the switch statement use default? Overall though I like how things are shaping up.

 
02. May 2011, 01:09 CET | Link
Georg Ragaller wrote on May 01, 2011 14:50:
The for loop has an optional fail block, which is executed when the loop completes normally Why is that optional block a fail block, when it's executed if the loop completes normally? Seems to be a contradiction to use fail as keyword here. By the way: I like all the rest ...

The idea is that you're using the for to search for a particular case, and if you fail to find it...

I'm not especially attached to fail. One possibility would be to follow Python and just use else.

Does that work better for you guys? I sure don't mind having one less keyword...

 
02. May 2011, 01:11 CET | Link
Marc Richards wrote on May 01, 2011 17:23:
I agree with Georg. fail seems off here. What about using default? Does the switch statement use default? Overall though I like how things are shaping up.

default is not a keyword. It's an annotation name that means the same thing as virtual in C#. I don't really want to keywordize it.

 
02. May 2011, 10:31 CET | Link
Georg Ragaller

As (almost) always it's easier to to complain, than to find a better solution ;)

Nevertheless, I would go rather with else than with fail, because of its more general meaning.

 
02. May 2011, 13:35 CET | Link
Georg Ragaller wrote on May 02, 2011 04:31:
As (almost) always it's easier to to complain, than to find a better solution ;) Nevertheless, I would go rather with else than with fail, because of its more general meaning.

Consider it officially under consideration :-)

 
03. May 2011, 01:07 CET | Link
Dan Berindei

Re: modules, have you considered dropping packages and importing modules directly like python and its dynamic brethren?

 
03. May 2011, 05:00 CET | Link
Re: modules, have you considered dropping packages and importing modules directly like python and its dynamic brethren?

Not something I've really given any thought to. I think it's reasonable that there be a unit of packaging/visibility smaller than the archive. There are plenty of modules that aggregate several self-contained subsystems. Now, in Java, where there is a tendency to declare everything either private or public, I think we don't really get the value out of packages that we should. But I think the scoping rules in Ceylon will encourage the use of package-private visibility, in order to control interdependencies between subsystems of the module. At least, that's the thinking behind the design.

Likewise, I bet that eventually there will be a need for a unit of packaging bigger than the archive. Let's call it an assembly that aggregates several modules. So there will be three levels of modularity:

package < module < assembly

Of course, some modules might consist of a single package. And some applications will consist of a single module. So you're not forced to use more than you need.

 
03. May 2011, 14:03 CET | Link
Andrey
An attribute is really just a local that happens to be captured by some shared declaration.

In the example below, count is not captured by a shared declaration, but, I believe, it should be considered an attribute:

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

    Natural doInc() {
        return ++count;
    }

    sahred Natural inc() {
        return doInc();
    }
}
 
03. May 2011, 14:32 CET | Link
An attribute is really just a local that happens to be captured by some shared declaration. In the example below, count is not captured by a shared declaration, but, I believe, it should be considered an attribute:

It's recursively captured :-)

 
06. Sep 2011, 02:49 CET | Link
Olivier Pernet

Have you considered changing the try/catch syntax to try/case? You already have a proper typecase in the standard switch/case statement, so you might want to reuse it for exception handling. As far as I can see, it's exactly the same thing.

 
06. Sep 2011, 09:44 CET | Link
Quintesse

@Olivier: then why change it? Ceylon tries to stay familiar to those coming from Java (and in a lesser way those from other C-like languages), now I agree that there are other things that seem to have been changed arbitrarily but I assure you that they did so for specific reasons. So you must come with some good arguments if you want them to listen :)

 
06. Sep 2011, 11:18 CET | Link
Olivier Pernet

The argument is regularity and simplicity of the grammar, which I understood is an important goal of Ceylon. But of course I understand familiarity for Java programmers is also important.

 
06. Sep 2011, 11:28 CET | Link
Olivier Pernet

Why have the resource-management version of try as a language primitive when you can define it in the standard library?

void try(Closeable resource, void do()) { try { do() } finally { resource.close() } }

...

try(resource) do { ... use resource ... }

or maybe with a different name:

using(resource) safely { ... use resource ... }

or

using(resource) do { ... use resource ... }

 
06. Sep 2011, 11:39 CET | Link
Olivier Pernet
I appreciate regularity, but I'm also not fond of `{ local Hi = Hello }`. How about `{ Hello => Hi }` ? Or as in Ada: `{ Hi renames Hello }`?
 
06. Sep 2011, 19:18 CET | Link

Oliver, case isn't actually an especially good fit, because a catch specifies a variable name, whereas a case does not. The syntax would be either be something like

try {
    something();
}
case (is MyException me) {
    swallow(me);
}
case (is YourException ye) {
    swallow(ye);
}
finally {
    finallySomething();
}

which does have the advantage of reusing a keyword, but is more verbose than catch, and actually irregular with the usual syntax of case (is ...) ... or, alternatively it would be something like

try {
    something();
}
catch (e)
case (is MyException) {
    swallow(me);
}
case (is YourException) {
    swallow(ye);
}
finally {
    finallySomething();
}

which I dislike on multiple levels, especially the verbosity, and doesn't actually even have the advantage of reusing a keyword.

I think the familiar syntax for catch is fine. That syntax has never been a problem for me in Java.

 
06. Sep 2011, 19:22 CET | Link
Olivier Pernet wrote on Sep 06, 2011 05:39:
I appreciate regularity, but I'm also not fond of
{ local Hi = Hello }
. How about
{ Hello => Hi }
? Or as in Ada:
{ Hi renames Hello }
?

Yeah, we already decided to change it. The syntax we settled on (and already implemented) is:

import ceylon.language { Seq=Sequence }
 
06. Sep 2011, 21:35 CET | Link
Olivier Pernet
Gavin King wrote on Sep 06, 2011 13:22:
Yeah, we already decided to change it. The syntax we settled on (and already implemented) is:
import ceylon.language { Seq=Sequence }

Ah, I agreed with your earlier comment though:

Gavin King wrote on May 06, 2011 16:59:
We could probably live with { Hi
  • Hello }, but it's not totally visually obvious which is the identifier which is being defined.

I would suggest

import ceylon.language { Sequence => Seq }

which I find less ambiguous.

Post Comment