Ceylon Might Just be the Only (JVM) Language that Got Nulls Right

Here we go again. THAT TOPIC.

But hang on. The approach discussed here (and in the Ceylon language) is not something you see every day. At the same time, it is very cunning.

Nulls are baked into the language

… or so it may seem. Indeed, in Ceylon, like in Kotlin (and possibly many other languages), there is a special type “annotation” that you can postfix to any reference type in order to make it nullable. For instance:

String firstName = "Homer";
String? middleName = "J";
String lastName = "Simpson";

In the above example, both firstName and lastName are mandatory values that can never be null, whereas middleName is optional. Most languages that support the above then ship with special operators to access the optional value, e.g. ?. in Ceylon and also in Kotlin.

// Another optional value:
Integer? length = middleName?.length;

// A non-optional value:
Integer length = middleName?.length else 0;

So, what is it about Ceylon that works so smoothly?

The thing that Ceylon got very right is the fact that all of the above is just syntactic sugar that is:

  • Easy to use
  • Maps well to our mindset, where null still is a thing
  • Can interoperate with Java
  • Doesn’t introduce cognitive friction

For us Java folks, we can still pretend that null is an OK-ish, hard to avoid thing (as we’ve claimed before on this blog). But what is null really? Is it the absent value? The unknown value? The uninitialised value?

Java only has one null thingy, and it is (ab-)used for all of the previous things, and more, when in theory, it is only really the uninitialised value, nothing more. On the other hand, when working with JDBC (and thus, SQL), it implicitly means the unknown value (with all the related caveats).

In Ceylon, however, Null is a special type, similar to Void in Java. The only value that can be assigned to the Null type is null:

// Ceylon
Null x = null;

// Java
Void x = null;

But the big difference is, null cannot be assigned to any other type! Wait. Couldn’t we assign null to String? … ? Of course, the following is possible in Ceylon:

String? x = null;

But why is this possible? Because String? is just syntax sugar for String|Null, a union type, i.e. a type that is either the String type or the Null type.

Huh, what are union types?

Let’s look at this more closely. When in the jOOQ API you want to work with SQL functions and expressions, there is always a great set of overloads that provide you with a standard version, and a convenience version where you can pass a bind variable. Take the equals operator, for instance:

interface Field<T> {
    Condition eq(Field<T> field);
    Condition eq(T value);
}

The above overloads allow you for writing things like the following, without needing to think about the distinction between a SQL expression and a Java bind variable (which is ultimately also a SQL expression):

// Comparing a column with bind variable
.where(BOOK.ID.eq(1))

// Comparing a column with another column expression
.and(BOOK.AUTHOR_ID.eq(AUTHOR.ID))

In fact, there are even more overloads, because the right hand side of a comparison operation can have other expressions as well, for instance:

interface Field<T> {
    Condition eq(Field<T> field);
    Condition eq(T value);
    Condition eq(Select<? extends Record1<T>> query);
    Condition eq(QuantifiedSelect<? extends Record1<T>> query);
}

Now, the same set of overloads needs to be repeated for not equals, greater than, greater or equal, etc. Wouldn’t it be nice to be able to express this “right-hand-side” thingy as a single, reusable type? I.e. a union type of all of the above types?

interface Field<T> {
    Condition eq(
        Field<T>
      | T
      | Select<? extends Record1<T>>
      | QuantifiedSelect<? extends Record1<T>> thingy
    );
}

Or even

// This is called a type alias. Another awesome
// Ceylon language feature (pseudo syntax)
alias Thingy => 
    Field<T>
  | T
  | Select<? extends Record1<T>>
  | QuantifiedSelect<? extends Record1<T>>;

interface Field<T> {
    Condition eq(Thingy thingy);
}

After all, that’s also how the SQL language is defined. Heck, that’s how any BNF notation defines syntactic elements. For instance:

<predicate> ::=
    <comparison predicate>
  | <between predicate>
  | <in predicate>
  | <like predicate>
  | <null predicate>
  | <quantified comparison predicate>
  | <exists predicate>
  | <unique predicate>
  | <match predicate>
  | <overlaps predicate>

OK, granted, a syntactic element is not strictly the same thing as a type, but the intuitive perception is the same.

Oh, and Java has union types, too!

In a brief flash of revelation, the Java 7 expert groups added support for union types in exception handling. You can write things like:

try {
    ...
}
catch (IOException | SQLException e) {
    // e can be any of the above!
}

And you can emulate union types with generics, which don’t support union types but intersection types in Java.

Back to Ceylon and NULL

Ceylon has gotten Null right. Because, historically, a nullable type is a type that can be the “real” type or the “null” value. We want that. We Java developers crave that. We cannot live without the soothing option of this kind of optional.

But the excellent thing about this approach is that it is extendable. What if I really need to distinguish between “unknown”, “uninitialised”, “undefined”, “42”? I can. Using types. Here’s a String that can model all of the aforementioned “special values”:

String|Unknown|Uninitialised|Undefined|FortyTwo

And if that’s too verbose, I just assign a name to it

interface TheStringToRuleThemAll
  => String|Unknown|Uninitialised|Undefined|FortyTwo;

But it cannot be Null. Because I don’t want it to be that value, that is everything and nothing. Are you convinced? I bet you are. From now on:

Don’t trust any language that pretends that the Option(al) monad is a decent approach at modelling null. It isn’t.

― me. Just now

Why? Let me illustrate. Kotlin/Ceylon/Groovy style syntax sugar using the elvis operator (regardless of the backing null semantics):

String name = bob?.department?.head?.name

Same thing with Optional monads:

Optional<String> name = bob
    .flatMap(Person::getDepartment)
    .map(Department::getHead)
    .flatMap(Person::getName);

AND POOR YOU IF YOU MIX UP map() WITH flatMap() JUST ONCE!!

Some people claim

Using union types is like driving around in a brand new Ferrari with your mother-in-law in the passenger seat.

by Elvira

Sure. But I claim: Well done, Ceylon. Let’s hope we’ll get union types in Java, too, outside of catch blocks!

Further reading

Liked this article? How about:

An Ingenious Workaround to Emulate an Application of Union Types in Java

Before I move on with the actual article, I’d like to give credit to Daniel Dietrich, author of the awesome Javaslang library, who has had the idea before me:

Contravariant Generic Bounds

It all started with a tweet:

I wanted to do something like pattern-matching a common super type of a set of types, along the lines of:

<T super T1 | T2 | ... | TN>

Note that what I really wanted is support for union types, not intersection types as I originally claimed.

Why did I want to do that? Because it would be a nice addition to the jOOλ library, which features typesafe tuples for Java:

class Tuple3<T1, T2, T3> {
    final T1 v1;
    final T2 v2;
    final T3 v3;

    // Lots of useful stuff here
}

What would be nice in a tuple is something like a forEach() method that iterates over all attributes:

tuple(1, "a", null).forEach(System.out::println);

The above would simply yield:

1
a
null

Now, what would this forEach() method’s argument type be? It would have to look like this:

class Tuple3<T1, T2, T3> {
    void forEach(Consumer<? super T1 | T2 | T3> c) {}
}

The consumer would receive an object that is of type T1 or T2 or T3. But a consumer that accepts a common super type of the previous three types is OK as well. For example, if we have:

Tuple2<Integer, Long> tuple = tuple(1, 2L);
tuple.forEach(v -> 
    System.out.println(v.doubleValue()));

The above would compile, because Number (or, more formally, Number & Comparable<?> is a common super type of Integer and Long, and it contains a doubleValue() method.

As soon as we’re adding e.g. a String to the tuple, the following will no longer compile:

Tuple3<Integer, Long, String> tuple = 
    tuple(1, 2L, "A");

// Doesn't compile
tuple.forEach((Number v) -> 
    System.out.println(v.doubleValue()));

Unfortunately, this is not possible in Java

Java currently supports union types (see also algebraic data types) only for exception catch blocks, where you can write things like:

interface X {
    default void print() {}
}
class X1 extends RuntimeException implements X {}
class X2 extends RuntimeException implements X {}

// With the above
try {
    ...
}
catch (X1 | X2 e) {
    // This compiles for the same reasons!
    e.print();
}

But unfortunately, catch blocks are the only place in Java that allows for using properties of union types.

This is where Daniel’s clever and cunning workaround comes into play. We can write a static method that performs some “pattern-matching” (if you squint) using generics, the other way round:

static <
    T, 
    T1 extends T, 
    T2 extends T, 
    T3 extends T
> 
void forEach(
    Tuple3<T1, T2, T3> tuple, 
    Consumer<? super T> consumer
) {
    consumer.accept(tuple.v1);
    consumer.accept(tuple.v2);
    consumer.accept(tuple.v3);
}

The above can now be used typesafely to infer the common super type(s) of T1, T2, and T3:

Tuple2<Integer, Long> t = tuple(1, 2L);
forEach(t, c -> {
    System.out.println(c.doubleValue());
});

yielding, as expected:

1.0
2.0

It makes sense, because the generic type constraints are simply specified “the other way round”, i.e. when T1 extends T, forcibly, T super T1

If you squint really hard ;-)

This technique is supposedly used by Daniel in Javaslang’s upcoming pattern matching API. We’re looking forward to seeing that in action!

Did you like this article?

Read also the following ones: