Thursday 23 October 2008

Life in the Fan lane - less NPEs

Since I last blogged I've been contributing to the development of the Fan Fantom language through lots of discussions. One of the latest features to be added is targeting a major reduction in NPEs.

Fantom and NPEs

The Fantom programming language is a relatively new language for the JVM (it compiles to bytecodes). However, it also compiles to .NET CLR making it very portable, with its own set of APIs.

Its a language that is easy for Java and C# developers to grasp (familiar syntax and concepts). It also fixes up many of the weak points identified in Java, with closures properly designed in, understandable generics, much less boilerplate and clever concurrency. Further, it performs close to, or as well as Java, because it is a statically typed language.

// find files less than one day old
files = dir.list.findAll |File f->Bool| {
  return - f.modified < 1day
// print the filenames to stdout
files.each |File f| {
  echo("$f.modified.toLocale:  $")

When I first came across the language, I immediately saw its potential as a Java successor. It is an evolutionary language. Simple for Java developers to move to, yet with clear potential productivity gains. One thing irked me though, and that was the handling of nulls, because Fantom had a model exactly like Java, where any variable could be null.

This has changed recently however, and now Fantom supports both nullable and not-null types:

 Str surname      // a variable or field that cannot hold null
 Str? middleName  // a variable or field that can hold null

At a stroke, this allows much more control within your application of the presence of null. No longer do we have to write in JavaDoc (or lengthy nasty annotations) as to the null status of a variable.

For example, you cannot compare surname to null, as that makes no sense. In the Java defensive coding style, it is often the case that variables are needlessly checked for null. This additional code gets in the way of the real business logic, and requires extra tests and analysis. With Fantom's null/not-null choice, a not-null variable can simply be relied on to be not-null, and any attempt to compare a not-null variable to null is a compile error.

Further to this, the type system allows you to block the presence of null in lists and maps. This is often an area forgotten when passing collections to methods - until the NPE strikes.

 Str[] list     // a list that cannot hold null
 Str:Int map    // a map that holds non-null strings to non-null integers
 Str:Int? map2  // a map that holds non-null strings to nullable integers

Finally, the null-safe operators (?., ?-> and ?:) are all prevented on operating on not-null variables. The ?. operator is a null-safe method invoke, that returns null if the LHS is null, so clearly this makes no sense if the LHS is a not-null variable.

One point to note is that not-null is the default. Why is that?

Well, it turns out that is the most common state for variable. Most programmers intend most variables to not hold null. In converting the Fantom sourcebase figures of 80% were not uncommon (ie. 80% of variables were originally intended to never hold null). Clearly it makes sense to make the most common case the default, and thus to make handling null a special case.

And what does the nullable/not-null variable actually represent? Well, most variables are objects, so once it gets to bytecode there is no difference. But for Int and Float, the non-null types can be converted to the primitive bytecodes for Java's long and double. This means that Fantom now has access to primitive level performance at the bytecode level which is going to allow Fantom applications to speed along nicely.


Fantom is coming along nicely. NPEs are a major headache in todays systems, and Fantom's approach will eliminate many of those errors. Further, it will eliminate much of the defensive code that is added to protect against values that will never actually be null - leaving Fantom even more decluttered.

Opinions welcome on NPE handling - something that seems to have been a complete lack of priority in Java.