Imagine a bacon-wrapped Ferrari. Still not better than our free technical reports.
See all our reports

The Adventurous Developer’s Guide to JVM languages – Ceylon

Last week Simon challenged me with his own implementation of HTTP server in Kotlin. Challenge accepted! So I picked Ceylon, the programming language developed by Red Hat and tried to implement the same. Ceylon is fairly new programming language and haven’t reached its final 1.0 version yet.

ceylon_logo

As a Java developer, I expected to to be able to adopt Ceylon very quickly, and in some sense I succeeded. Basically, to start coding it is enough to walk through the Tour of Ceylon at the project homepage – you will get most of the information about the language from there.

The Ceylon source code for the HTTP server that I have written for this experiment is available at GitHub. Feel free to comment, suggest and submit pull requests – I’ll be happy to learn from you.

First impressions

The very first thing we will notice about Сeylon is that its “infrastructural” part is very well prepared. By this I mean modules. Ceylon runtime is based on JBoss Modules and Ceylon uses modules for everything. Even your brand new project is actually a Ceylon module, which is declared via module.ceylon file in the project directory:

module com.zt '1.0.0' {
  import ceylon.interop.java '0.4.1';
  import java.base '7';
}

This means that my module is called com.zt and its version is 1.0.0. One thing to notice about the module declaration format is that the module name is not in quotes and the version is in quotes, e.g. ‘1.0.0’. That is a bit strange since the version is a number and package isn’t. Why not to skip the quotes for the version? Probably because version might contain non-numeric characters, like 1.0.0-SNAPSHOT. But then I would skip the quotes all for good.

Next, the main file of the project is run.ceylon and we can just implement run() method to launch our app:

void run(){
   print("Hello from Ceylon!");
}

Methods (or functions) in Ceylon can be standalone and do not belong to any class. Just like in Kotlin. Same applies to attributes. Actually, I was curious what it compiles to. What I learned is that every standalone attribute (or field) and method are compiled into a dedicated class. That means that the following code is compiled into 2 classes – port_.class and run_.class – which will also include the public static void main method.

import java.net { ServerSocket, Socket }
import java.lang { Thread }
 
shared Integer port = 8080;
 
void run(){
   ServerSocket server = ServerSocket(port);
   print("Listening for connections on port " port "...");
 
   while(true){
   	Socket socket = server.accept();
   	print("New client connection accepted!");
   	HTTPServer httpServer = HTTPServer(socket);
 
   	Thread threadRunner = Thread(httpServer);
        threadRunner.start();   
   }
}

When I was trying to understand this part I discovered that Ceylon tooling compiles and packages everything into Ceylon ARchive (car) and cleans up after itself, so you won’t find class files after compilation – I had to unpack the archive to get access to the class files.

One nice part of the tooling was that when I added a new dependency into module.ceylon file the dependency was automatically downloaded from Ceylon Herd and resolved by IDE. That’s another win from the infrastructure of the language.

Java Interoperability

Java interoperability was a tricky part for me. First of all, you have to add dependency into module.ceylon to be able to use Java classes: import java.base '7';

When you’re using Java classes in Ceylon, intuitively you would expect all the same methods to be available on the Java class instance as if you would normally use it from Java program. In Ceylon it is not like that. Here’s an example:

value printWriter = PrintWriter(socket.outputStream);

I would expect to be able to use getOutputStream() method on socket, but Ceylon interprets it as a getter for a field and replaces its semantics with a property access (perhaps?). Even if getOutputStream() isn’t really a getter, if you check its source.

ceylon-ide

The second challenge I encountered was about type conversion. There’s a special mapping between Ceylon types and Java types.

So fileData must be of a byte array type, and according to the type mapping rules it should be declared as Array<Integer> in Ceylon. OK, so did:

Array<Integer> fileData = arrayOfSize { size = fileLength; element = 0; };

Oh my.. no luck:

Exception in thread "Thread-0" java.lang.ClassCastException: [J cannot be cast to [B
at com.zt.HTTPServer.run(classes.ceylon:59)
at java.lang.Thread.run(Thread.java:722)

Apparently, for type conversion to work correctly I needed to import special Java interoperability module: import ceylon.interop.java ‘0.4.1’, and then use a special method provided by this module. Here’s the code that I needed the type conversion for:

Array<Integer> fileData = createByteArray(fileLength);
value fileIn = FileInputStream(file);
fileIn.read(fileData);

Basically, after all this issues I’ve got my HTTP server implementation running but there was once more surprise ahead waiting for me at runtime: null checks.

When I’ve got my application running and submitted the first GET request, it failed with the following stack trace.

Exception in thread "Thread-1" java.lang.NullPointerException
    at com.redhat.ceylon.compiler.java.Util.checkNull(Util.java:478)
    at com.zt.HTTPServer.run(classes.ceylon:19)

The surprising part was that at classes.ceylon:19 I had the following line for code:

String input = bufferedReader.readLine();

This is where some knowledge of how to read decompiled code using javap helped me. The bytecode revealed that Ceylon compiler inserts null checks after the returns from methods of Java classes. I had to use '?' suffix for the declaration to fix that:

String? input = bufferedReader.readLine();

But then there’s a problem of how this value is used. For instance, if it is used as a parameter for StringTokenizer, then instantiating the class would fail as well. So I just made a lame fix to make it work:

if(!exists input){ 
  return;
}

I’m sure there should be a better way on how to handle this situation despite it solved my immediate problem.

What did I learn?

The syntax and the keywords of Ceylon are quite different from the ones that I’m used to in Java. However, it did not disturb me that much. In most cases it was enough to associate the keyword from Ceylon with the one form Java, for instance, satisfies in Ceylon vs implements in Java.

Overall, it was quite an interesting experience. The impression I’ve got from Ceylon is that it is focused on the style of OOP and infrastructure (module system) rather than on conciseness of the expressions, not trying to squeeze a million of meanings into a one-liner. It actually means that Java verbosity is not something that Ceylon creators would be worried about, it is rather the ecosystem and language design at large that is a focus.

Psst! If you liked this post, we wrote a 50-page RebelLabs report on Java 8, Scala, Groovy, Fantom, Clojure, Ceylon, Kotlin & Xtend.

Get the FULL REPORT on all 8 JVM languages

  • arhan

    As it turns out, with this experiment I’ve hit a little bug in Ceylon compiler: https://groups.google.com/d/topic/ceylon-dev/ZKGNQbECo6g/discussion
    This is quite good actually – I’m happy that this kind of experiments lead to a contribution :)

  • Tako Schotanus

    Hi Anton, first of all nice article!

    And second, as you requested I repeat the comments I made on your Ceylon Community post (https://plus.google.com/u/0/communities/110888482566246585625/stream/f6d6f4f3-88fb-476a-8639-b844098a51f5):

    – Ceylon compiles directly to .car files so there’s no “cleaning up” to do (but yes that means you need to unpack if you want to take a look at what’s inside)

    – Nulls are indeed handled in a special way on the “edge” of Ceylon and Java. Because we don’t know enough about the Java code to be able to assure that it won’t return null and because in your code you said you were expecting a String (and only a String, not null) we insert a check “to make sure”. The idea is that we prefer to have it “blow up” sooner than later (possibly in code far away from the actual problem site). But maybe an explanatory message could be added to the exception when it fails.

    – In the same way interop with Java should work great most of the time, but it has its warts because Java has more types than we do. The class cast exception is a bit ugly though, so we are sure to take a look at that.

    – And finally, we do indeed improve greatly on the verbosity of Java we think, but not in a way that we try to let you types as little symbols/letters as possible (so we still have words like “satisfies”) because we think readability and understandability-at-first-glance is very important. But we do try to give you the tools to say things in a concise way, writing as little code as possible.

  • http://in.relation.to/Bloggers/Gavin Gavin King

    Thanks for the article/feedback, Anton. We’re currently arguing about/working on this issue of interop with primitive arrays. Actually it’s a truly painful corner case. Our usual strategy for handling Java’s primitive types is unsound for container types, which is usually perfectly fine, since Java generics don’t abstract over Java primitive types: you can’t have a List. But then Java has arrays, which are this special case container type which _do_ abstract over primitive types, and so we need a special case of a special case to deal with them. Like I said, we’re on it.

  • arhan

    Thanks for the comment, Gavin. Actually, for my case specifically, a library like ceylon-io would solve the problem. The more common use libraries there will be, the less of Java interop corner cases will appear, I believe.

  • http://in.relation.to/Bloggers/Gavin Gavin King

    P.S. I meant to let you know about “assert”. If you have a case where you _really_ don’t want to do something with the null case, you can just write “assert (exists input);” and that will narrow input to a non-null type.

  • Mark Ramirez

    I already have read about “Ceylon” ( Cool elephant logo, by the way ), but as soon a saw the “module” keyword, smell it as a good coffee … (umlcat)

  • ddekany

    I think that, in fact, they did worry about Java verbosity. Surely, just to tease developers :->, they force you to type `variable` instead of the familiar `var`, etc. But then, there’s no boilerplate anymore like the field + getter method + setter method insanity in Java, or the almost as annoying constructor-that-assign-to-fields-of-the-same-name-as-the-parameters thing. Even if Eclipse will generate these for you, they make reading and modifying source code more expensive. (Surely the next Java could easily introduce sugar for these, but it seems they won’t do it, ever.) They also get rid of instanceof-chek-then-cast, etc. And you will need much less null checks, as the compiler does that for you most of the time (the Java interop NPE doesn’t count – you have also explicitly told Ceylon there that the return value won’t be null, but it was). And so on. I’m sure most applications shrink significantly in source code size if you rewrite them in Ceylon.