views:

779

answers:

7

I'm designing a language, and I'm wondering if it's reasonable to make reference types non-nullable by default, and use "?" for nullable value and reference types. Are there any problems with this? What would you do about this:

class Foo {
    Bar? b;
    Bar b2;
    Foo() {
        b.DoSomething(); //valid, but will cause exception
        b2.DoSomething(); //?
    }
}
A: 

A couple of examples of similar features in other languages:

There's also Nullable<T> (from C#) but that is not such a good example because of the different treatment of reference vs. value types.

In your example you could add a conditional message send operator, e.g.

b?->DoSomething();

To send a message to b only if it is non-null.

finnw
+7  A: 

My current language design philosophy is that nullability should be something a programmer is forced to ask for, not given by default on reference types (in this, I agree with Tony Hoare - Google for his recent QCon talk).

On this specific example, with the unnullable b2, it wouldn't even pass static checks: Conservative analysis cannot guarantee that b2 isn't NULL, so the program is not semantically meaningful.

My ethos is simple enough. References are an indirection handle to some resource, which we can traverse to obtain access to that resource. Nullable references are either an indirection handle to a resource, or a notification that the resource is not available, and one is never sure up front which semantics are being used. This gives either a multitude of checks up front (Is it null? No? Yay!), or the inevitable NPE (or equivalent). Most programming resources are, these days, not massively resource constrained or bound to some finite underlying model - null references are, simplistically, one of...

  • Laziness: "I'll just bung a null in here". Which frankly, I don't have too much sympathy with
  • Confusion: "I don't know what to put in here yet". Typically also a legacy of older languages, where you had to declare your resource names before you knew what your resources were.
  • Errors: "It went wrong, here's a NULL". Better error reporting mechanisms are thus essential in a language
  • A hole: "I know I'll have something soon, give me a placeholder". This has more merit, and we can think of ways to combat this.

Of course, solving each of the cases that NULL current caters for with a better linguistic choice is no small feat, and may add more confusion that it helps. We can always go to immutable resources, so NULL in it's only useful states (error, and hole) isn't much real use. Imperative technqiues are here to stay though, and I'm frankly glad - this makes the search for better solutions in this space worthwhile.

Adam Wright
+3  A: 

Have a look at the Elvis operator proposal for Java 7. This does something similar, in that it encapsulates a null check and method dispatch in one operator, with a specified return value if the object is null. Hence:

String s = mayBeNull?.toString() ?: "null";

checks if the String s is null, and returns the string "null" if so, and the value of the string if not. Food for thought, perhaps.

Brian Agnew
Why is this called the Elvis operator?
Zifre
Good question, and one I didn't know the answer to. However see http://www.infoq.com/articles/groovy-1.5-new re. the operator ?:, which is related. A little tenuous, nonetheless...
Brian Agnew
I can only assume that it's some joke about what to do if the object has "left the building"?
Christian Hayter
In .NET code, I find that constantly checking for a null object just so I can call a method is a real PITA, and an operator like this would certainly help. It's very tempting to define extension methods that do something intelligent for null values, even though that's not how you are supposed to use them. :-(
Christian Hayter
@Zifre, @Christian Hayter, @Brian Agnew: I know this is way late... but look at it sideways, and think about Elvis' hair! I'll help by adding a mouth: ?:)
Derrick Turk
+3  A: 

Having reference types be non-nullable by default is the only reasonable choice. We are plagued by languages and runtimes that have screwed this up; you should do the Right Thing.

Brian
A: 

I think null values are good: They are a clear indication that you did something wrong. If you fail to initialize a reference somewhere, you'll get an immediate notice.

The alternative would be that values are sometimes initialized to a default value. Logical errors are then a lot more difficult to detect, unless you put detection logic in those default values. This would be the same as just getting a null pointer exception.

Lennaert
It's sometimes necessary to initialize something without knowing the value it will eventually take. This is relatively rare, though. Usually, initialization takes place on the same line as the declaration. Also, the proposal doesn't eliminate null values, just makes them non-null by default.
kvb
Additionally, it's not true that you'll get an immediate notice. You'll get an exception as soon as you try to dereference the null value, but that exception may be caught, and you'll only find out at runtime, not compile time.
kvb
Problem is, you can't force programmers to initialize a variable correcty: How many times have you seen:DataSet ds = new DataSet();...ds = ReadDataSet( ... );in code?If ds were non-nullable, no error would be detected if the call to ReadDataSet() were forgotten.
Lennaert
Point is that bad/inexperienced programmers _will_ make these mistakes. Or just make every variable nullable. Better programmers will make these mistakes less often. And if someone is stupid enough to catch Exception or NPE, well, that's his own fault.
Lennaert
+2  A: 

This feature was in Spec#. They defaulted to nullable references and used ! to indicate non-nullables. This was because they wanted backward compatibility.

In my dream language (of which I'd probably be the only user!) I'd make the same choice as you, non-nullable by default.

I would also make it illegal to use the . operator on a nullable reference (or anything else that would dereference it). How would you use them? You'd have to convert them to non-nullables first. How would you do this? By testing them for null.

In Java and C#, the if statement can only accept a bool test expression. I'd extend it to accept the name of a nullable reference variable:

if (myObj)
{
    // in this scope, myObj is non-nullable, so can be used
}

This special syntax would be unsurprising to C/C++ programmers. I'd prefer a special syntax like this to make it clear that we are doing a check that modifies the type of the name myObj within the truth-branch.

I'd add a further bit of sugar:

if (SomeMethodReturningANullable() into anotherObj)
{
    // anotherObj is non-nullable, so can be used
}

This just gives the name anotherObj to the result of the expression on the left of the into, so it can be used in the scope where it is valid.

I'd do the same kind of thing for the ?: operator.

string message = GetMessage() into m ? m : "No message available";

Note that string message is non-nullable, but so are the two possible results of the test above, so the assignment is value.

And then maybe a bit of sugar for the presumably common case of substituting a value for null:

string message = GetMessage() or "No message available";

Obviously or would only be validly applied to a nullable type on the left side, and a non-nullable on the right side.

(I'd also have a built-in notion of ownership for instance fields; the compiler would generate the IDisposable.Dispose method automatically, and the ~Destructor syntax would be used to augment Dispose, exactly as in C++/CLI.)

Spec# had another syntactic extension related to non-nullables, due to the problem of ensuring that non-nullables had been initialized correctly during construction:

class SpecSharpExampleClass
{
    private string! _nonNullableExampleField;

    public SpecSharpExampleClass(string s)
        : _nonNullableExampleField(s) 
    {

    }
}

In other words, you have to initialize fields in the same way as you'd call other constructors with base or this - unless of course you initialize them directly next to the field declaration.

Daniel Earwicker
+1  A: 

Aren't you happy with the currently existing programming languages? :p

Dimitri C.
No, not at all (the only language that is even close to ideal is Haskell, and C# is pretty nice for an imperative language).
Zifre