www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - (non)nullable types

reply Brian <digitalmars brianguertin.com> writes:
On Mon, 09 Feb 2009 04:25:55 +0300, Denis Koroskin wrote:

 So, let's ask the community: Would you like to see nullable types in D?
 
 http://www.micropoll.com/akira/mpview/539369-138652 (please, don't abuse
 by voting multiple time)
 
 Explain your reasoning in newsgroups. Thank you.

i vote yes, i would absolutely love non-nullable types. in some cases i even use dummy objects to avoid null checks.
Feb 08 2009
next sibling parent "Nick Sabalausky" <a a.a> writes:
 On Mon, 09 Feb 2009 04:25:55 +0300, Denis Koroskin wrote:

 So, let's ask the community: Would you like to see nullable types in D?

 http://www.micropoll.com/akira/mpview/539369-138652 (please, don't abuse
 by voting multiple time)

 Explain your reasoning in newsgroups. Thank you.


Yes: You don't always need an object to be nullable, and in those cases, having it nullable only serves to create a need for extra checking (Take a look at the ANTLR/StringBuilder Java source to see what happens when nullability goes too far. Disclaimer: I don't mean that as a jab at ANTLR/StringBuilder or the people behind it). However, I could be swayed to change my mind against it if it were shown that nullable/nonnullable could not be done without ending up with a bunch of compatibility/conversion/API problems. BTW, May I politely refer whoever used/made micropoll to the "OT: Scripting on websites [Was: Re: QtD 0.1 is out!]" sub-discussion over on digitalmars.D.announce? ;-)
Feb 08 2009
prev sibling next sibling parent reply Jason House <jason.james.house gmail.com> writes:
Brian wrote:

 On Mon, 09 Feb 2009 04:25:55 +0300, Denis Koroskin wrote:
 
 So, let's ask the community: Would you like to see nullable types in D?
 
 http://www.micropoll.com/akira/mpview/539369-138652 (please, don't abuse
 by voting multiple time)
 
 Explain your reasoning in newsgroups. Thank you.

i vote yes, i would absolutely love non-nullable types. in some cases i even use dummy objects to avoid null checks.

Please, please give us non-nullable types! Use of a non-nullable reference type before initialization could be an error just like it is in C#. I hate segfaults and this would at least allow safer code to be written. Non-nullable types should be implicitly castable to nullable types when making function calls that don't support them.
Feb 08 2009
parent reply bearophile <bearophileHUGS lycos.com> writes:
Jason House:
 Non-nullable types should be implicitly castable to nullable types when making
function
 calls that don't support them.

An explicit cast may be better/safer. Bye, bearophile
Feb 09 2009
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
bearophile wrote:
 Jason House:
 Non-nullable types should be implicitly castable to nullable types when making
function
 calls that don't support them.

An explicit cast may be better/safer.

Non-nullable types are proper subtypes of nullable types. There is no added safety in requiring a cast. Andrei
Feb 09 2009
parent bearophile <bearophileHUGS lycos.com> writes:
Andrei Alexandrescu:
 Non-nullable types are proper subtypes of nullable types. There is no 
 added safety in requiring a cast.

Yes, sorry, often I misunderstood (I was thinking of the opposite situation). Bye, bearophile
Feb 09 2009
prev sibling next sibling parent reply Alex Burton <alexibu mac.com> writes:
I vote yes.

I was pretty sure the topic was refering to the ability to specify a pointer
that could not be set to 0.

I totally agree with this rule, and enforce it in all my C++ code with smart
pointers.

class X
{

};

X x; //in D
or X * x; //in C++

Setting x to zero or any other value that is not the address of an instance of
X is bad bad bad, and should be discouraged at all costs.

If I ask for a pointer to an instance of X and you give me a zero or 0x0123456
or anything else, it is like mixing in a bicycle when a recipe asked for a cup
of olive oil.

This is a huge source of bugs where people best described as C programmers do
C++.

In D the mistake can be made especially when a struct changes into a class, and
allocations are not changed. Like X x has to change to X x = new X (which is a
bit repetitive in D IMHO).
I think the default should be to have the native X x (D pointer to class) to be
instantiating the class, and the X x = new Y should only be required when X and
Y are different (inherited) types and 
X x(32); when the class X has a ctor with arguments ( actually why don't I
start a new thread about this)
There are many better ways to write code than using nullable pointers.
Feb 09 2009
parent bearophile <bearophileHUGS lycos.com> writes:
Alex Burton:
 ( actually why don't I start a new thread about this)

I suggest you to start a new thread (and to try to explain yourself a bit better). Bye, bearophile
Feb 09 2009
prev sibling next sibling parent Michel Fortin <michel.fortin michelf.com> writes:
On 2009-02-08 23:19:55 -0500, Brian <digitalmars brianguertin.com> said:

 On Mon, 09 Feb 2009 04:25:55 +0300, Denis Koroskin wrote:
 
 So, let's ask the community: Would you like to see nullable types in D?
 
 http://www.micropoll.com/akira/mpview/539369-138652 (please, don't abuse
 by voting multiple time)
 
 Explain your reasoning in newsgroups. Thank you.

i vote yes, i would absolutely love non-nullable types. in some cases i even use dummy objects to avoid null checks.

Same here. A non-nullable pointer, when used as a function argument, is a contract the compiler can enforce at the call site. If 80% of your functions accept only non-nullable pointers, then it means you can remove about 80% of your checks for null without worries of seeing your program crash. For global and member variables, non-nullable pointers means you can get an error exactly where the infringing code first put a null where there shouldn't be one; not later when someone tries to access it. And I think non-nullable should be the default because it's safer and it'd force people to be explicit when they want to take the responsability to work with nullable ones. So it's a yes. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Feb 09 2009
prev sibling next sibling parent Christopher Wright <dhasenan gmail.com> writes:
Brian wrote:
 On Mon, 09 Feb 2009 04:25:55 +0300, Denis Koroskin wrote:
 
 So, let's ask the community: Would you like to see nullable types in D?

 http://www.micropoll.com/akira/mpview/539369-138652 (please, don't abuse
 by voting multiple time)

 Explain your reasoning in newsgroups. Thank you.

i vote yes, i would absolutely love non-nullable types. in some cases i even use dummy objects to avoid null checks.

This is called the Null Object Pattern. Often, you need a special instance or subclass to satisfy the NOP. For instance, my company's staff scheduling program might get a null EmployeeGroup class that would need a reference to the Customer object, unlike other EmployeeGroups, in order to claim that it contains all employees.
Feb 09 2009
prev sibling next sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Brian wrote:
 On Mon, 09 Feb 2009 04:25:55 +0300, Denis Koroskin wrote:
 
 So, let's ask the community: Would you like to see nullable types in D?

 http://www.micropoll.com/akira/mpview/539369-138652 (please, don't abuse
 by voting multiple time)

 Explain your reasoning in newsgroups. Thank you.

i vote yes, i would absolutely love non-nullable types. in some cases i even use dummy objects to avoid null checks.

Oh, and I vote no. I think it's needless complexity. I code without any special care for null objects, and I get a segfault or NullReferenceException maybe once a week, probably less. I've always been able to track down the bug very quickly. Having types be non-nullable by default would harm my productivity a fair bit. Having them be optionally non-nullable would be okay, as long as the libraries I use don't use non-nullable types.
Feb 09 2009
next sibling parent reply Sebastian Biallas <groups.5.sepp spamgourmet.com> writes:
Christopher Wright wrote:
 Oh, and I vote no. I think it's needless complexity. I code without any 
 special care for null objects, and I get a segfault or 
 NullReferenceException maybe once a week, probably less. I've always 
 been able to track down the bug very quickly.

That misses the point completely. It's not about you (as the programmer) getting NPEs; it's about the user getting bug-free software in the first place. In most cases a segfault/NPE is easy to track down for the programmer, I guess nobody would object to this. But once the software is shipped, it's a very serious problem: It's a situation the programmer didn't thought about so you usually have a data loss. I really like the approach of the Nice[1] language: Before you can access a nullable type you have to test whether it's null. After the test, the type is automagically converted to a nonnullable type. This language doesn't have a NPE, so you *know* that your software is bug-free in this regard. [1] http://nice.sf.net Sebastian
Feb 09 2009
next sibling parent "Nick Sabalausky" <a a.a> writes:
"Sebastian Biallas" <groups.5.sepp spamgourmet.com> wrote in message 
news:gmpsun$14dj$1 digitalmars.com...
 Christopher Wright wrote:
 Oh, and I vote no. I think it's needless complexity. I code without any 
 special care for null objects, and I get a segfault or 
 NullReferenceException maybe once a week, probably less. I've always been 
 able to track down the bug very quickly.

That misses the point completely. It's not about you (as the programmer) getting NPEs; it's about the user getting bug-free software in the first place. In most cases a segfault/NPE is easy to track down for the programmer, I guess nobody would object to this. But once the software is shipped, it's a very serious problem: It's a situation the programmer didn't thought about so you usually have a data loss. I really like the approach of the Nice[1] language: Before you can access a nullable type you have to test whether it's null. After the test, the type is automagically converted to a nonnullable type. This language doesn't have a NPE, so you *know* that your software is bug-free in this regard. [1] http://nice.sf.net Sebastian

Interesting, that is very nice, I like that idea a lot.
Feb 09 2009
prev sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Sebastian Biallas wrote:
 Christopher Wright wrote:
 Oh, and I vote no. I think it's needless complexity. I code without 
 any special care for null objects, and I get a segfault or 
 NullReferenceException maybe once a week, probably less. I've always 
 been able to track down the bug very quickly.

That misses the point completely. It's not about you (as the programmer) getting NPEs; it's about the user getting bug-free software in the first place. In most cases a segfault/NPE is easy to track down for the programmer, I guess nobody would object to this. But once the software is shipped, it's a very serious problem: It's a situation the programmer didn't thought about so you usually have a data loss.

Your language features should get rid of bugs that are easy to make and difficult to track down. It's not worthwhile to alter your language to remove easily found, easily fixed bugs. Logic errors are difficult to find. Your language cannot help you eliminate those (though contracts and unittests can help you find them). If you do not test your software extensively, you will ship software with logic errors. The testing required to find logic errors will also find low-level errors like segfaults.
Feb 11 2009
next sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Jarrett Billingsley wrote:
 On Wed, Feb 11, 2009 at 3:40 PM, Christopher Wright <dhasenan gmail.com> wrote:
 Your language features should get rid of bugs that are easy to make and
 difficult to track down. It's not worthwhile to alter your language to
 remove easily found, easily fixed bugs.

Except when someone passes a null reference into a large, complex data structure and that reference is passed around blindly and never dereferenced until somewhere far down the line, giving you a segfault in a program location that you never would have expected. By then it's much more difficult to figure out where that null reference came from. I've had a much larger share of these kinds of null reference errors than the "oops someone passed a null into my function and I didn't account for it" kind. Disallowing null references on most public interface functions from the beginning would make those kinds of bugs nonexistent. Otherwise, you have to put explicit runtime null checks on every public interface function, and woe to the guy who forgets to do that.

Or add an invariant. You know which field is null when it shouldn't be, so that should be simple enough. Or use a property rather than a field and add a contract. This covers the same cases that non-nullable types do, though admittedly with slightly less granularity. A complex datastructure might have a complex initialization process that is best spread across a few lines of code rather than happening all at once. If that is the case, you *can't* use non-nullable types, because that restriction isn't valid until the object is fully initialized. But you can still use contracts here.
Feb 11 2009
next sibling parent Sebastian Biallas <groups.5.sepp spamgourmet.com> writes:
Christopher Wright wrote:
 Or add an invariant. You know which field is null when it shouldn't be, 
 so that should be simple enough. Or use a property rather than a field 
 and add a contract.

A runtime check is completely different from a compile time check. Sebastian
Feb 11 2009
prev sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Jarrett Billingsley wrote:
 On Wed, Feb 11, 2009 at 5:23 PM, Christopher Wright <dhasenan gmail.com> wrote:
 Or add an invariant. You know which field is null when it shouldn't be, so
 that should be simple enough. Or use a property rather than a field and add
 a contract.

 This covers the same cases that non-nullable types do, though admittedly
 with slightly less granularity.

 A complex datastructure might have a complex initialization process that is
 best spread across a few lines of code rather than happening all at once. If
 that is the case, you *can't* use non-nullable types, because that
 restriction isn't valid until the object is fully initialized. But you can
 still use contracts here.

What is your response to the part of my post that was cut off? You know, the part about having to write stupid contracts and how the compiler can and should be checking this kind of thing?

You'll eventually need to convert between nullable(T) and not_nullable(T). This requires runtime checks. A struct NotNull(T) could do the same, with some significant disadvantages. But making not_nullable the default, I'm not sure how much work that would add for me. I know it would add a fair bit for Walter. Having it as an option and not the default would probably make it sufficiently annoying that people would not use it, in which case it's no better than adding an invariant. On the one hand, not_nullable by default would probably do me no good. On the other, it would cost me little effort in most cases, most likely, unless I got sufficiently annoyed at all the little cases that I gave up and made every variable nullable by default. It would be cool if we could accompany these discussions with sample compilers that implement the feature in question. That way, I could speak with some certainty when I said that a proposed feature would be annoying.
Feb 11 2009
next sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Jarrett Billingsley wrote:
 Except honestly, the number of cases where you _don't_ want something
 to be null far outweighs the number of cases where you _do_.  It's not
 exactly as insidious as the "throws" clause.

I see what you're saying, though I'm certain there is a large minority of cases in which I do want to allow nulls. I really want to see how painful it would be to have non-nullable types by default. There's also the matter of, can I use a nullable variable like a normal variable? For example: nullable Foo foo = something; foo.doStuff(); Brian mentioned having to check if the variable is null before using it. This would not be easy to implement, and it might be a bit hard to use. Again, I'd have to see it in use. While we're talking about hacking the compiler, how about const by default? :P
Feb 12 2009
next sibling parent grauzone <none example.net> writes:
 While we're talking about hacking the compiler, how about const by 
 default? :P

This would foil all my plans to ignore or actively circumvent the new const features. It's already very annoying to cast the string literals to the good old char[].
Feb 12 2009
prev sibling next sibling parent reply "Nick Sabalausky" <a a.a> writes:
"Denis Koroskin" <2korden gmail.com> wrote in message 
news:op.upazx0d4o7cclz proton.creatstudio.intranet...
 On Fri, 13 Feb 2009 21:43:22 +0300, Nick Sabalausky <a a.a> wrote:
 I still like this that someone else mentioned:

 T? x = something;
 if(x !is null)
 {
     // x is implicitly "T" here, not "T?"
 }
 else
 {
     // handle null condition (x is still "T?")
 }

Or use an existing syntax: Foo? foo = ...; if (Foo value = foo) { // value is a local variable that is only accessible if foo is not null } else { // value is not accessible here }

I could live with that, but I'd prefer my suggestion because it wouldn't require the creation of an extra label for what's essentially the same variable. With your code we'd just end up with a whole bunch of: Foo? myObj = ...; if (Foo nonnullMyObj = myObj) //etc... // or Foo? nullableMyObj = ...; if (Foo myObj = nullableMyObj) //etc... ...Which just seems unnecessary to me.
Feb 13 2009
next sibling parent reply "Nick Sabalausky" <a a.a> writes:
"Nick Sabalausky" <a a.a> wrote in message 
news:gn4g2n$fbf$1 digitalmars.com...
 "Denis Koroskin" <2korden gmail.com> wrote in message 
 news:op.upazx0d4o7cclz proton.creatstudio.intranet...
 On Fri, 13 Feb 2009 21:43:22 +0300, Nick Sabalausky <a a.a> wrote:
 I still like this that someone else mentioned:

 T? x = something;
 if(x !is null)
 {
     // x is implicitly "T" here, not "T?"
 }
 else
 {
     // handle null condition (x is still "T?")
 }

Or use an existing syntax: Foo? foo = ...; if (Foo value = foo) { // value is a local variable that is only accessible if foo is not null } else { // value is not accessible here }

I could live with that, but I'd prefer my suggestion because it wouldn't require the creation of an extra label for what's essentially the same variable. With your code we'd just end up with a whole bunch of: Foo? myObj = ...; if (Foo nonnullMyObj = myObj) //etc... // or Foo? nullableMyObj = ...; if (Foo myObj = nullableMyObj) //etc... ...Which just seems unnecessary to me.

Another thing to think about is delegates. Even if classes are non-null by default, we can still get the same old null reference problem with delegates: class Foo { void delegate() dg; void callDg() { dg(); // Oops! Forgot to check for null!! } } void main() { auto f = new Foo(); f.callDg(); } This could probably be solved by whatever mechanism is used for classes.
Feb 13 2009
parent Michel Fortin <michel.fortin michelf.com> writes:
On 2009-02-13 16:30:59 -0500, Jarrett Billingsley 
<jarrett.billingsley gmail.com> said:

 I was thinking nullability would be applicable to all reference types.
  A null pointer, delegate, array, or whatever is just as bad as a null
 reference.

That's right with me, except for arrays for which there is bound checking safeties in place. A null array and an empty array should be undistinguishable in my opinion. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Feb 13 2009
prev sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2009-02-13 14:01:57 -0500, "Nick Sabalausky" <a a.a> said:

 Or use an existing syntax:
 
 Foo? foo = ...;
 if (Foo value = foo) {
 // value is a local variable that is only accessible if foo is not
 null
 } else {
 // value is not accessible here
 }

I could live with that, but I'd prefer my suggestion because it wouldn't require the creation of an extra label for what's essentially the same variable. With your code we'd just end up with a whole bunch of: Foo? myObj = ...; if (Foo nonnullMyObj = myObj) //etc... // or Foo? nullableMyObj = ...; if (Foo myObj = nullableMyObj) //etc... ...Which just seems unnecessary to me.

The problem with retyping the same variable is that it may gives strange situations. For instance, is this an error? Foo? myObj = ...; if (myObj !is null) { doSomethingWith(myObj); myObj = null; // should this be an error? } And what about: Foo? myObj = ...; while (myObj !is null) myObj = myObj.next; If you want to avoid verbosity, you'll need to retype the variable until it gets assigned another potentially non-null value. Basically, you'll need to perform flow analysis inside the function to enforce that. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Feb 13 2009
parent reply "Nick Sabalausky" <a a.a> writes:
"Michel Fortin" <michel.fortin michelf.com> wrote in message 
news:gn4pl9$1217$1 digitalmars.com...
 On 2009-02-13 14:01:57 -0500, "Nick Sabalausky" <a a.a> said:

 Or use an existing syntax:

 Foo? foo = ...;
 if (Foo value = foo) {
 // value is a local variable that is only accessible if foo is not
 null
 } else {
 // value is not accessible here
 }

I could live with that, but I'd prefer my suggestion because it wouldn't require the creation of an extra label for what's essentially the same variable. With your code we'd just end up with a whole bunch of: Foo? myObj = ...; if (Foo nonnullMyObj = myObj) //etc... // or Foo? nullableMyObj = ...; if (Foo myObj = nullableMyObj) //etc... ...Which just seems unnecessary to me.

Foo? myObj = ...; if (myObj !is null) { doSomethingWith(myObj); myObj = null; // should this be an error? }

-------- // This (a more generalized case of above)... Foo? myObj = ...; if(myObj !is null) { bar1(myObj); if(blah1 > blah2) myObj = null; // Yes, error bar2(); } // ...would change to this... Foo? myObj = ...; bool turnToNull=false; if(myObj !is null) { bar1(myObj); if(blah1 > blah2) turnToNull = true; } if(turnToNull) { myObj = null; bar2(); } --------
 And what about:

 Foo? myObj = ...;
 while (myObj !is null)
     myObj = myObj.next;

-------- Foo? myObj = ...; while(true) { if(myObj.next !is null) myObj = myObj.next; else break; } myObj = null; //this line optional // Or, if you don't like "while(true)", // you could use a "bool done" flag. -------- With Denis's syntax, I'm not sure how the second one would be possible, but the first one would certainly be nicer. So I'm tempted to say that both syntaxes should be allowed... However, you do have a valid point that my syntax has a problem whenever assigning a possibly-null value to the original nullable variable from within the scope of the null-check. It can definitely be worked around, but I am coming to realize that maybe removing a variable's nullability shouldn't happen implicitly unless it can also be added back implicity - which would require flow analysis. Between that and the difficultly of Denis's syntax handling that simple while loop example, I guess full-blown flow-analysis would be needed after all in order to realistically eliminate null reference errors on nullable types. Dang!
Feb 14 2009
next sibling parent reply Yigal Chripun <yigal100 gmail.com> writes:
Denis Koroskin wrote:
 On Sat, 14 Feb 2009 11:39:17 +0300, Nick Sabalausky <a a.a> wrote:

 "Michel Fortin" <michel.fortin michelf.com> wrote in message
 news:gn4pl9$1217$1 digitalmars.com...
 On 2009-02-13 14:01:57 -0500, "Nick Sabalausky" <a a.a> said:

 Or use an existing syntax:

 Foo? foo = ...;
 if (Foo value = foo) {
 // value is a local variable that is only accessible if foo is not
 null
 } else {
 // value is not accessible here
 }

I could live with that, but I'd prefer my suggestion because it wouldn't require the creation of an extra label for what's essentially the same variable. With your code we'd just end up with a whole bunch of: Foo? myObj = ...; if (Foo nonnullMyObj = myObj) //etc... // or Foo? nullableMyObj = ...; if (Foo myObj = nullableMyObj) //etc... ...Which just seems unnecessary to me.

Foo? myObj = ...; if (myObj !is null) { doSomethingWith(myObj); myObj = null; // should this be an error? }

-------- // This (a more generalized case of above)... Foo? myObj = ...; if(myObj !is null) { bar1(myObj); if(blah1 > blah2) myObj = null; // Yes, error bar2(); } // ...would change to this... Foo? myObj = ...; bool turnToNull=false; if(myObj !is null) { bar1(myObj); if(blah1 > blah2) turnToNull = true; } if(turnToNull) { myObj = null; bar2(); } --------
 And what about:

 Foo? myObj = ...;
 while (myObj !is null)
 myObj = myObj.next;

-------- Foo? myObj = ...; while(true) { if(myObj.next !is null)

Dang! NullPointerException, because you never checked myObj against null
 myObj = myObj.next;
 else
 break;
 }
 myObj = null; //this line optional

 // Or, if you don't like "while(true)",
 // you could use a "bool done" flag.
 --------

 With Denis's syntax, I'm not sure how the second one would be possible,

Foo? myObj = ...; if (Foo obj = myObj) { while (true) { if (auto next = obj.next) { obj = next; } else { break; } } }
 but
 the first one would certainly be nicer. So I'm tempted to say that both
 syntaxes should be allowed...

 However, you do have a valid point that my syntax has a problem whenever
 assigning a possibly-null value to the original nullable variable from
 within the scope of the null-check. It can definitely be worked
 around, but
 I am coming to realize that maybe removing a variable's nullability
 shouldn't happen implicitly unless it can also be added back implicity -
 which would require flow analysis.

 Between that and the difficultly of Denis's syntax handling that simple
 while loop example, I guess full-blown flow-analysis would be needed
 after
 all in order to realistically eliminate null reference errors on nullable
 types. Dang!

I don't think code flow analysis the way you suggest is useful. I certainly don't want my variable types to be changed at some point after null-check. Code flow analysis would be nice to have, but in a different way: Foo b; // not initialized, can not be read yet if (condition) { b = createFoo(42); } else if (otherCondition) { b = new Foo(); } else { // return b; // error, b is write only return; } // b is both read/write accessible at this point

can't this be done with: Foo b = condition ? createFoo(42) : (otherCondition ? new Foo() : return); // use b
Feb 14 2009
parent reply Yigal Chripun <yigal100 gmail.com> writes:
Denis Koroskin wrote:
 On Sat, 14 Feb 2009 15:42:57 +0300, Yigal Chripun <yigal100 gmail.com>
 wrote:

 Denis Koroskin wrote:
 On Sat, 14 Feb 2009 11:39:17 +0300, Nick Sabalausky <a a.a> wrote:

 "Michel Fortin" <michel.fortin michelf.com> wrote in message
 news:gn4pl9$1217$1 digitalmars.com...
 On 2009-02-13 14:01:57 -0500, "Nick Sabalausky" <a a.a> said:

 Or use an existing syntax:

 Foo? foo = ...;
 if (Foo value = foo) {
 // value is a local variable that is only accessible if foo is not
 null
 } else {
 // value is not accessible here
 }

I could live with that, but I'd prefer my suggestion because it wouldn't require the creation of an extra label for what's essentially the same variable. With your code we'd just end up with a whole bunch of: Foo? myObj = ...; if (Foo nonnullMyObj = myObj) //etc... // or Foo? nullableMyObj = ...; if (Foo myObj = nullableMyObj) //etc... ...Which just seems unnecessary to me.

Foo? myObj = ...; if (myObj !is null) { doSomethingWith(myObj); myObj = null; // should this be an error? }

-------- // This (a more generalized case of above)... Foo? myObj = ...; if(myObj !is null) { bar1(myObj); if(blah1 > blah2) myObj = null; // Yes, error bar2(); } // ...would change to this... Foo? myObj = ...; bool turnToNull=false; if(myObj !is null) { bar1(myObj); if(blah1 > blah2) turnToNull = true; } if(turnToNull) { myObj = null; bar2(); } --------
 And what about:

 Foo? myObj = ...;
 while (myObj !is null)
 myObj = myObj.next;

-------- Foo? myObj = ...; while(true) { if(myObj.next !is null)

Dang! NullPointerException, because you never checked myObj against null
 myObj = myObj.next;
 else
 break;
 }
 myObj = null; //this line optional

 // Or, if you don't like "while(true)",
 // you could use a "bool done" flag.
 --------

 With Denis's syntax, I'm not sure how the second one would be possible,

Foo? myObj = ...; if (Foo obj = myObj) { while (true) { if (auto next = obj.next) { obj = next; } else { break; } } }
 but
 the first one would certainly be nicer. So I'm tempted to say that both
 syntaxes should be allowed...

 However, you do have a valid point that my syntax has a problem
 whenever
 assigning a possibly-null value to the original nullable variable from
 within the scope of the null-check. It can definitely be worked
 around, but
 I am coming to realize that maybe removing a variable's nullability
 shouldn't happen implicitly unless it can also be added back
 implicity -
 which would require flow analysis.

 Between that and the difficultly of Denis's syntax handling that simple
 while loop example, I guess full-blown flow-analysis would be needed
 after
 all in order to realistically eliminate null reference errors on
 nullable
 types. Dang!

I don't think code flow analysis the way you suggest is useful. I certainly don't want my variable types to be changed at some point after null-check. Code flow analysis would be nice to have, but in a different way: Foo b; // not initialized, can not be read yet if (condition) { b = createFoo(42); } else if (otherCondition) { b = new Foo(); } else { // return b; // error, b is write only return; } // b is both read/write accessible at this point

can't this be done with: Foo b = condition ? createFoo(42) : (otherCondition ? new Foo() : return); // use b

It solves the simplest examples, but not more complex ones, so it is not generic enough.

In what way it's not generic enough? IMO, this is not a matter of flow analysis at all, but rather a matter of if statement syntax. IMO "if" needs to be expression instead of a statement. also See http://nemerle.org/Quick_Guide#Decisions
Feb 14 2009
parent reply bearophile <bearophileHUGS lycos.com> writes:
Yigal Chripun:
 IMO "if" needs to be expression instead of a statement.
 also See http://nemerle.org/Quick_Guide#Decisions

If you want to turn D into a more functional language, then that may be positive. But I think D will not change so much, so better for you to stick to things that are more likely to change. Bye, bearophile
Feb 14 2009
parent reply "Nick Sabalausky" <a a.a> writes:
"bearophile" <bearophileHUGS lycos.com> wrote in message 
news:gn6ism$1rgc$1 digitalmars.com...
 Yigal Chripun:
 IMO "if" needs to be expression instead of a statement.
 also See http://nemerle.org/Quick_Guide#Decisions

If you want to turn D into a more functional language, then that may be positive. But I think D will not change so much, so better for you to stick to things that are more likely to change.

D's already becoming more functional. Evidence: pure, immutable, std.algorithm.
Feb 14 2009
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Nick Sabalausky wrote:
 "bearophile" <bearophileHUGS lycos.com> wrote in message 
 news:gn6ism$1rgc$1 digitalmars.com...
 Yigal Chripun:
 IMO "if" needs to be expression instead of a statement.
 also See http://nemerle.org/Quick_Guide#Decisions

positive. But I think D will not change so much, so better for you to stick to things that are more likely to change.

D's already becoming more functional. Evidence: pure, immutable, std.algorithm.

...std.functional. Andrei
Feb 14 2009
next sibling parent "Nick Sabalausky" <a a.a> writes:
"Andrei Alexandrescu" <SeeWebsiteForEmail erdani.org> wrote in message 
news:gn7447$51u$1 digitalmars.com...
 Nick Sabalausky wrote:
 "bearophile" <bearophileHUGS lycos.com> wrote in message 
 news:gn6ism$1rgc$1 digitalmars.com...
 Yigal Chripun:
 IMO "if" needs to be expression instead of a statement.
 also See http://nemerle.org/Quick_Guide#Decisions

positive. But I think D will not change so much, so better for you to stick to things that are more likely to change.

D's already becoming more functional. Evidence: pure, immutable, std.algorithm.

...std.functional. Andrei

Sorry, I'm still in D1-land ;-)
Feb 14 2009
prev sibling parent reply Yigal Chripun <yigal100 gmail.com> writes:
Andrei Alexandrescu wrote:
 Nick Sabalausky wrote:
 "bearophile" <bearophileHUGS lycos.com> wrote in message
 news:gn6ism$1rgc$1 digitalmars.com...
 Yigal Chripun:
 IMO "if" needs to be expression instead of a statement.
 also See http://nemerle.org/Quick_Guide#Decisions

be positive. But I think D will not change so much, so better for you to stick to things that are more likely to change.

D's already becoming more functional. Evidence: pure, immutable, std.algorithm.

...std.functional. Andrei

Besides, such a change will allow us to remove the ugly ?: operator and will free the ? sign. One of my favorite Ruby features (besides Blocks) is that Ruby allows using ? and ! as part of identifiers. This is used in Ruby by convention to represent methods that answer questions, and methods that are more "dangareous" like in-place modification of data. so you can write: if (range.more?) .. and it also has array.sort vs. array.sort! where the former gives a sorted copy and the latter sorts in-place.
Feb 14 2009
parent reply "Nick Sabalausky" <a a.a> writes:
"Yigal Chripun" <yigal100 gmail.com> wrote in message 
news:gn771i$ded$1 digitalmars.com...
 Besides, such a change will allow us to remove the ugly ?: operator and 
 will free the ? sign.

I like the ?: operator :-). It's short. But I agree it might be worth switching to an expression "if...else..." if that meant we could do the following...
 One of my favorite Ruby features (besides Blocks) is that Ruby allows 
 using ? and ! as part of identifiers. This is used in Ruby by convention 
 to represent methods that answer questions, and methods that are more 
 "dangareous" like in-place modification of data.

 so you can write:
 if (range.more?) ..

When I was looking into Ruby, I was impressed by this too. I had always liked MS's convention of prepending "is" to flags, but that only works on certain flags (sometimes you really mean "has" or "exists" or "capable of" something else besides "is"). Appending "?" is much more general.
 and it also has array.sort vs. array.sort! where the former gives a sorted 
 copy and the latter sorts in-place.

It would be kind of handy to have a nice simple convention for in-place vs non-inplace functions. Obviously "!" wouldn't work for D though, unless we were masochistic enough to revisit the old "What syntax to use for templates?" issue.
Feb 14 2009
parent Yigal Chripun <yigal100 gmail.com> writes:
Nick Sabalausky wrote:
 "Yigal Chripun"<yigal100 gmail.com>  wrote in message
 news:gn771i$ded$1 digitalmars.com...
 Besides, such a change will allow us to remove the ugly ?: operator and
 will free the ? sign.

I like the ?: operator :-). It's short. But I agree it might be worth switching to an expression "if...else..." if that meant we could do the following...
 One of my favorite Ruby features (besides Blocks) is that Ruby allows
 using ? and ! as part of identifiers. This is used in Ruby by convention
 to represent methods that answer questions, and methods that are more
 "dangareous" like in-place modification of data.

 so you can write:
 if (range.more?) ..

When I was looking into Ruby, I was impressed by this too. I had always liked MS's convention of prepending "is" to flags, but that only works on certain flags (sometimes you really mean "has" or "exists" or "capable of" something else besides "is"). Appending "?" is much more general.
 and it also has array.sort vs. array.sort! where the former gives a sorted
 copy and the latter sorts in-place.

It would be kind of handy to have a nice simple convention for in-place vs non-inplace functions. Obviously "!" wouldn't work for D though, unless we were masochistic enough to revisit the old "What syntax to use for templates?" issue.

if it'll ever change, I remember that long thread about this very subject.. there's another way to mark in-place vs. copy which I don't like as much - I think it's used in Python: array.sort vs. array.sorted where the first form is in-place and the other is a copy. the convention is to use adjectives for copies - if you say array.sort (with a verb) you actually sort the *current* array. this is more fragile IMO than just using a "!" since you need to be familiar with English grammar. (for non-native English speakers that can be an issue) e.g: egg.rot vs. egg.rotten, etc..
Feb 14 2009
prev sibling parent "Nick Sabalausky" <a a.a> writes:
"Denis Koroskin" <2korden gmail.com> wrote in message 
news:op.upcaojexo7cclz korden-pc...
 On Sat, 14 Feb 2009 11:39:17 +0300, Nick Sabalausky <a a.a> wrote:

 "Michel Fortin" <michel.fortin michelf.com> wrote in message
 news:gn4pl9$1217$1 digitalmars.com...
 On 2009-02-13 14:01:57 -0500, "Nick Sabalausky" <a a.a> said:

 Or use an existing syntax:

 Foo? foo = ...;
 if (Foo value = foo) {
 // value is a local variable that is only accessible if foo is not
 null
 } else {
 // value is not accessible here
 }

I could live with that, but I'd prefer my suggestion because it wouldn't require the creation of an extra label for what's essentially the same variable. With your code we'd just end up with a whole bunch of: Foo? myObj = ...; if (Foo nonnullMyObj = myObj) //etc... // or Foo? nullableMyObj = ...; if (Foo myObj = nullableMyObj) //etc... ...Which just seems unnecessary to me.

Foo? myObj = ...; if (myObj !is null) { doSomethingWith(myObj); myObj = null; // should this be an error? }

-------- // This (a more generalized case of above)... Foo? myObj = ...; if(myObj !is null) { bar1(myObj); if(blah1 > blah2) myObj = null; // Yes, error bar2(); } // ...would change to this... Foo? myObj = ...; bool turnToNull=false; if(myObj !is null) { bar1(myObj); if(blah1 > blah2) turnToNull = true; } if(turnToNull) { myObj = null; bar2(); } --------
 And what about:

 Foo? myObj = ...;
 while (myObj !is null)
     myObj = myObj.next;

-------- Foo? myObj = ...; while(true) { if(myObj.next !is null)

Dang! NullPointerException, because you never checked myObj against null
         myObj = myObj.next;
     else
         break;
 }
 myObj = null; //this line optional

 // Or, if you don't like "while(true)",
 // you could use a "bool done" flag.
 --------


I blame fatigue ;) -------- Foo? myObj = ...; if(myObj !is null) { while(true) { if(myObj.next !is null) myObj = myObj.next; else break; } } myObj = null; //this line optional --------
 I don't think code flow analysis the way you suggest is useful. I 
 certainly don't want my variable types to be changed at some point after 
 null-check.

I'm not sure if we're on the same page here. What I was thinking about code flow analysis was this: Foo? maybeGetObj() {...} void takesAFoo(Foo f) {...} Foo? f = maybeGetObj(); // Ok, f *is* permanently "Foo?" f.mFunc(); // Illegal, might be null takesAFoo(f); // Illegal, might be null // Tells compiler f is now implicitly convertable to "Foo" if(f !is null) { f.mFunc(); // Ok takesAFoo(f); // Ok // With flow-analysis, this is ok and tells // compiler f is no longer implicitly // convertable to "Foo" f = maybeGetObj(); f.mFunc(); // Illegal takesAFoo(f); // Illegal f = new Foo(); // now implicitly convertable to "Foo" f.mFunc(); // Ok takesAFoo(f); // Ok // Ok, but possible warning that the condition's // result will always be the same (true in this case). if(f !is null) {...} f = null; // no longer implicitly convertable to "Foo" } else // f is still *not* implicitly convertable to "Foo" { f = new Foo(); // now implicitly convertable to "Foo" } // Depending which branch of the if was taken, // f might be implicitly convertable to "Foo", // or it might not be. So, from here, assume // that it *isn't* implicitly convertable to "Foo". Foo f2=...; // Ok, f2 *is* permanently "Foo" // Illegal, f2 *is* "Foo" f2 = null; // Error, comparison of incompatible types if(f2 !is null) {...}
 Code flow analysis would be nice to have, but in a different way:

 Foo b; // not initialized, can not be read yet

 if (condition) {
    b = createFoo(42);
 } else if (otherCondition) {
    b = new Foo();
 } else {
    // return b; // error, b is write only
    return;
 }

 // b is both read/write accessible at this point

Agreed, that would be nice to have.
Feb 14 2009
prev sibling next sibling parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Denis Koroskin wrote:
 [snip]
 I still like this that someone else mentioned:

 T? x = something;
 if(x !is null)
 {
     // x is implicitly "T" here, not "T?"
 }
 else
 {
     // handle null condition (x is still "T?")
 }

Or use an existing syntax: Foo? foo = ...; if (Foo value = foo) { // value is a local variable that is only accessible if foo is not null } else { // value is not accessible here }

Both of these syntaxes are solving a problem that doesn't exist. This is why we have null dereference exceptions: accessing a null pointer is an error. All this is doing is moving the onus for the check from the hardware to the programmer. Leave magic out of the language and let the hardware do it's job. If you have a nullable type, it's because you WANT it to be nullable, and you shouldn't have to stand on one leg and jump through a burning hoop every time you want to look at the damn thing. The point here is not to make using null harder, it's to make not using null easier. -- Daniel
Feb 13 2009
next sibling parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Jarrett Billingsley wrote:
 On Fri, Feb 13, 2009 at 5:52 PM, Daniel Keep
 <daniel.keep.lists gmail.com> wrote:
 Both of these syntaxes are solving a problem that doesn't exist.  This
 is why we have null dereference exceptions: accessing a null pointer is
 an error.  All this is doing is moving the onus for the check from the
 hardware to the programmer.

 Leave magic out of the language and let the hardware do it's job.  If
 you have a nullable type, it's because you WANT it to be nullable, and
 you shouldn't have to stand on one leg and jump through a burning hoop
 every time you want to look at the damn thing.

Having only nullable references with no non-null counterpart *SNAP*

This is (expletive) ridiculous. Why is it that I'm unable to criticise even a tiny aspect of someone's proposal without people disregarding everything I've said TIME AND AGAIN over the past year or more that I'm in support of non-nullable references and go "oh but we need non-nullable references"?! It's like if I don't 100% agree with someone, I'm automatically completely opposed to their position. I'm opposed to having to check nullable types every goddamn time you want to look at its value. Screw it. -- Daniel
Feb 13 2009
parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Sorry, Jarrett.  You're right; it was uncalled for.  I was having a bad
day, and that was just the straw that broke the camel's back.  My
comments were slightly ambiguous, but I was still upset that trying to
comment on a particular syntax or subfeature of non-nullable types
results in me being chucked bodily into the "hates non-nullable types"
camp.  Again.  :P

To clarify: I am, and always have been, in full support of non-nullable
types, preferably by default.  What I object to specifically in this
case is the requirement to always check that a nullable value is not
null every time it is used.

We have hardware null-dereference exceptions for this.

Another issue is that this subthread is talking about increasingly more
complicated analysis by the compiler to try and determine if a given
nullable value has been checked yet or not.  I don't think this is the
job of the compiler; if I have a nullable value, it's because I wanted a
nullable value, and I should be ready to deal with it appropriately.

Finally, this also creates a distinction between nullable types and
everything else in the type system.  For example, the following test
isn't enforced by the compiler.

int a,b;
// set a and b
if( b != 0 ) int c = a / b;

Maybe it should be, but unless the compiler is -very- smart about it AND
it applies to all types that have potentially "unsafe" values for a
given operation, I don't want to see nullable types singled out.

  -- Daniel
Feb 15 2009
next sibling parent Don <nospam nospam.com> writes:
Daniel Keep wrote:
 To clarify: I am, and always have been, in full support of non-nullable
 types, preferably by default.  What I object to specifically in this
 case is the requirement to always check that a nullable value is not
 null every time it is used.
 
 We have hardware null-dereference exceptions for this.
 
 Another issue is that this subthread is talking about increasingly more
 complicated analysis by the compiler to try and determine if a given
 nullable value has been checked yet or not.  I don't think this is the
 job of the compiler; if I have a nullable value, it's because I wanted a
 nullable value, and I should be ready to deal with it appropriately.
 
 Finally, this also creates a distinction between nullable types and
 everything else in the type system.

I am in complete agreement with you on all these points. Nullable types (int? *) and making non-nullable the default would add huge benefits; but I think others are proposing significant complexity for very small extra gain.
Feb 16 2009
prev sibling parent reply Sergey Gromov <snake.scaly gmail.com> writes:
Mon, 16 Feb 2009 13:26:29 +1100, Daniel Keep wrote:

 To clarify: I am, and always have been, in full support of non-nullable
 types, preferably by default.  What I object to specifically in this
 case is the requirement to always check that a nullable value is not
 null every time it is used.

Hmm, I think I've got your point. The nullable types should work exactly as they work now, with the same consequences. You don't need to check for null to dereference, and you crash if you dereference null. The non-nullable types are a separate territory. Again, you don't need to check for null to dereference, and you never crash because there is never null. You need an explicit run-time check to move from null to non-null territory, but that's all you need, and only when you really want it. I think it's my favorite design so far.
Feb 16 2009
parent reply Christopher Wright <dhasenan gmail.com> writes:
Sergey Gromov wrote:
 Mon, 16 Feb 2009 13:26:29 +1100, Daniel Keep wrote:
 
 To clarify: I am, and always have been, in full support of non-nullable
 types, preferably by default.  What I object to specifically in this
 case is the requirement to always check that a nullable value is not
 null every time it is used.

Hmm, I think I've got your point. The nullable types should work exactly as they work now, with the same consequences. You don't need to check for null to dereference, and you crash if you dereference null. The non-nullable types are a separate territory. Again, you don't need to check for null to dereference, and you never crash because there is never null. You need an explicit run-time check to move from null to non-null territory, but that's all you need, and only when you really want it. I think it's my favorite design so far.

One possible change: implicit casting with an assertion that the nullable value is not null. I'm not sure whether this is a good idea. On the one hand, it's easier for the programmer to use nullable types in that case; on the other, it encourages people not to have error handling. I favor implicit casting, for now. It shouldn't be difficult to try it both ways.
Feb 17 2009
next sibling parent reply "Nick Sabalausky" <a a.a> writes:
"Christopher Wright" <dhasenan gmail.com> wrote in message 
news:gnfgj6$1484$2 digitalmars.com...
 One possible change: implicit casting with an assertion that the nullable 
 value is not null.

I can tell right now I wouldn't like that. That would make it far too easy to make mistakes, as it would open up a way for mistakes to circumvent the whole point of having non-nullables. If I accidentially tried to provide a nullable to something that expected a non-nullable, I'd want some sort of up-front notice so that I can either fix it or confirm "yes, I really did mean that" rather than have to hope that I'm lucky enough for the value to actually be null when I test it. An implicit cast should either "just work" with no risk of runtime-error, or be disallowed in favor of something more explicit.
Feb 17 2009
parent reply Christopher Wright <dhasenan gmail.com> writes:
Nick Sabalausky wrote:
 "Christopher Wright" <dhasenan gmail.com> wrote in message 
 news:gnfgj6$1484$2 digitalmars.com...
 One possible change: implicit casting with an assertion that the nullable 
 value is not null.

I can tell right now I wouldn't like that. That would make it far too easy to make mistakes, as it would open up a way for mistakes to circumvent the whole point of having non-nullables. If I accidentially tried to provide a nullable to something that expected a non-nullable, I'd want some sort of up-front notice so that I can either fix it or confirm "yes, I really did mean that" rather than have to hope that I'm lucky enough for the value to actually be null when I test it. An implicit cast should either "just work" with no risk of runtime-error, or be disallowed in favor of something more explicit.

I think I favor this, actually. If you don't care, you can cast manually. But the implementation difference should be miniscule.
Feb 17 2009
parent reply Christopher Wright <dhasenan gmail.com> writes:
Christopher Wright wrote:
 Nick Sabalausky wrote:
 "Christopher Wright" <dhasenan gmail.com> wrote in message 
 news:gnfgj6$1484$2 digitalmars.com...
 One possible change: implicit casting with an assertion that the 
 nullable value is not null.

I can tell right now I wouldn't like that. That would make it far too easy to make mistakes, as it would open up a way for mistakes to circumvent the whole point of having non-nullables. If I accidentially tried to provide a nullable to something that expected a non-nullable, I'd want some sort of up-front notice so that I can either fix it or confirm "yes, I really did mean that" rather than have to hope that I'm lucky enough for the value to actually be null when I test it. An implicit cast should either "just work" with no risk of runtime-error, or be disallowed in favor of something more explicit.

I think I favor this, actually. If you don't care, you can cast manually. But the implementation difference should be miniscule.

One problem here is static constructors. They're supposed to run in import order, I believe, so if you do this: module A; Object o; static this () { o = new Object; } module B; import A; static this () { writeln(o); } That should be safe. I think. Class member variables are also problematic. It's probably going to be annoying to make sure each nullable variable gets initialized in every constructor. I could push that off to runtime, of course, but that isn't so great. Struct member variables are the killer, unless you have a struct constructor.
Feb 18 2009
parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Christopher Wright wrote:
 One problem here is static constructors. They're supposed to run in
 import order, I believe, so if you do this:
 
 module A;
 Object o;
 static this () { o = new Object; }
 
 module B;
 import A;
 static this () { writeln(o); }
 
 That should be safe. I think.
 
 Class member variables are also problematic. It's probably going to be
 annoying to make sure each nullable variable gets initialized in every
 constructor. I could push that off to runtime, of course, but that isn't
 so great.
 
 Struct member variables are the killer, unless you have a struct
 constructor.

Mmm... non-nullable types would also be the only type in D that cannot have it's own initialiser assigned to it. This is actually something I ran across trying to make a non-nullable struct; it can't be done because you can't force a variable to be non-default initialised. I'm not sure what the solution would be. A few thoughts: * Allow types to be flagged, within the compiler, as being "must-set." This includes all non-nullable types AND any aggregates that contain non-nullable types. If they're local variables, they have to be assigned to at declaration, and if they're members, the have to be assigned to within the constructor. * Make these types the exception and don't give them an init property. Generic code will either have to test for its absence, fail for non-nullable types, or be written differently. * Alternately, have an init property which is invalid and can't be assigned. Probably worse since you can't easily test for it. * If using the T? syntax to mean "nullable T," generic code could be written to use T? for all classes, then do the conversion to non-nullable at return time. * What should the following output? class T {} static if( is( T? : T ) ) pragma(msg, "T? : T"); static if( is( T : T? ) ) pragma(msg, "T : T?"); -- Daniel
Feb 18 2009
parent Christopher Wright <dhasenan gmail.com> writes:
Daniel Keep wrote:
 
 Christopher Wright wrote:
 One problem here is static constructors. They're supposed to run in
 import order, I believe, so if you do this:

 module A;
 Object o;
 static this () { o = new Object; }

 module B;
 import A;
 static this () { writeln(o); }

 That should be safe. I think.

 Class member variables are also problematic. It's probably going to be
 annoying to make sure each nullable variable gets initialized in every
 constructor. I could push that off to runtime, of course, but that isn't
 so great.

 Struct member variables are the killer, unless you have a struct
 constructor.

Mmm... non-nullable types would also be the only type in D that cannot have it's own initialiser assigned to it.

You could have the compiler use the default constructor to create an instance and assign that to Type.init, but that is evil.
 This is actually something I ran across trying to make a non-nullable
 struct; it can't be done because you can't force a variable to be
 non-default initialised.
 
 I'm not sure what the solution would be.  A few thoughts:
 
 * Allow types to be flagged, within the compiler, as being "must-set."
 This includes all non-nullable types AND any aggregates that contain
 non-nullable types.  If they're local variables, they have to be
 assigned to at declaration, and if they're members, the have to be
 assigned to within the constructor.

Determining this is a bit ugly...I think I said that.
 * Make these types the exception and don't give them an init property.
 Generic code will either have to test for its absence, fail for
 non-nullable types, or be written differently.

No init property by default, but you could easily supply one. It wouldn't be CTFE-friendly, though.
 * Alternately, have an init property which is invalid and can't be
 assigned.  Probably worse since you can't easily test for it.

You can; see below. But the test isn't obvious.
 * If using the T? syntax to mean "nullable T," generic code could be
 written to use T? for all classes, then do the conversion to
 non-nullable at return time.

Nullable seems to be a storage class. T? will be inconsistent as such. Additionally, I am certainly not going to implement nullable value types, and T? is the C# syntax for nullable value types, so that is inconsistent. One advantage of using "nullable T" rather than "T?" is IsExpressions: static if (is (T == nullable)) ... The alternative will probably be necessary, as well: static if (is (T U == nullable U)) ...
 * What should the following output?
 
   class T {}
 
   static if( is( T? : T ) ) pragma(msg, "T? : T");
   static if( is( T : T? ) ) pragma(msg, "T : T?");

T is implicitly convertible to nullable(T). There is some debate on whether the opposite should be allowed. So that will either print: T : T? or: T? : T T : T?
Feb 18 2009
prev sibling parent Michel Fortin <michel.fortin michelf.com> writes:
On 2009-02-17 18:17:55 -0500, Christopher Wright <dhasenan gmail.com> said:

 One possible change: implicit casting with an assertion that the 
 nullable value is not null. I'm not sure whether this is a good idea. 
 On the one hand, it's easier for the programmer to use nullable types 
 in that case; on the other, it encourages people not to have error 
 handling.

I think it's a good idea: good enough to be useful, simple enough to be implemented without much hassle. Once we have enough code using non-nullable, it'll be easier to evaluate the impacts of adding compile-time constrains for nullables, and whether it's worth it or not. I wouldn't make it just like an assertion though. I'd make it something separate you can deactiave with a compiler switch, just like bound checking. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Feb 17 2009
prev sibling next sibling parent reply "Nick Sabalausky" <a a.a> writes:
"Daniel Keep" <daniel.keep.lists gmail.com> wrote in message 
news:gn4tjj$197g$1 digitalmars.com...
 Denis Koroskin wrote:
 [snip]
 I still like this that someone else mentioned:

 T? x = something;
 if(x !is null)
 {
     // x is implicitly "T" here, not "T?"
 }
 else
 {
     // handle null condition (x is still "T?")
 }

Or use an existing syntax: Foo? foo = ...; if (Foo value = foo) { // value is a local variable that is only accessible if foo is not null } else { // value is not accessible here }

Both of these syntaxes are solving a problem that doesn't exist. This is why we have null dereference exceptions: accessing a null pointer is an error. All this is doing is moving the onus for the check from the hardware to the programmer. Leave magic out of the language and let the hardware do it's job. If you have a nullable type, it's because you WANT it to be nullable, and you shouldn't have to stand on one leg and jump through a burning hoop every time you want to look at the damn thing. The point here is not to make using null harder, it's to make not using null easier.

The entire point of all of this is to make dereferencing null (and I mean an actual null, not "nullable") either difficult or (preferably) impossible. The reason we want to do that is that dereferencing null is a runtime error and we want to eliminate runtime errors whenever possible, by either eliminating their possiblity or by turning them into compile-time errors. Most null reference errors can be outright eliminated via non-null types, and the remaining cases can be turned into compile-time errors by forcing a check. Why would anyone want a runtime error when they could get a compile-time error instead? The "burning hoop", as you describe it, of checking a nullable var for null before dereferencing is just simply something that the programmer should already be doing anyway. Take the following case: void Foo(void delegate()? dg) //nullable { dg(); } If the programmer has a deliberate reason to *want* the dg paramater to be nullable, then why in the would would they ever want to dereference it like that without first checking for null? Just because they *want* their program to be spitting out runtime errors? I'm sorry, but I just don't buy that. (And the reason can't possibly be because they expected any possible nulls to be handled in the calling code, because that's exactly the type of scenario that non-nullables are intended for in the first place.) Also, just to be clear, no one (as far as I'm aware) is advocating anything like this: class Foo { private void delegate()? dg; // nullable public void setDg(void delegate()? dg) // nullable { if(dg !is null) // I don't think anyone is expecting this check to be required this.dg = dg; } } The forced check would only be on dereferencing and converting to non-nullable.
Feb 13 2009
parent reply Christopher Wright <dhasenan gmail.com> writes:
Nick Sabalausky wrote:
 The "burning hoop", as you describe it, of checking a nullable var for null 
 before dereferencing is just simply something that the programmer should 
 already be doing anyway. Take the following case:
 
 void Foo(void delegate()? dg) //nullable
 {
     dg();
 }

You're right for that small example. Now let's say you have an object a dozen methods referencing the same nullable member. They're private methods called by public methods that check if the member is null first. I don't want to have to check whether the variable is null every single time I use it; I want to do it once at the start and assume (since it's a single-threaded application and I'm not assigning to that member) that that check works for everything. I'm just fine with getting a segfault when using a nullable variable that's null. That's expected behavior.
Feb 14 2009
next sibling parent Michel Fortin <michel.fortin michelf.com> writes:
On 2009-02-14 08:37:04 -0500, Christopher Wright <dhasenan gmail.com> said:

 I'm just fine with getting a segfault when using a nullable variable 
 that's null. That's expected behavior.

I leaning to think it'd be best to have non-nullable pointers that just throw an exception when you attempt to assign null to them. That way you can have implicit conversions from nullable to non-nullable pointers where the compiler would add a check for null and throw; you no longer have to check for null for arguments that shouldn't be null (declaring the argument as non-nullable would force the caller to make sure it is not null, which cost nothing if the caller is passing a non-nullable variable); and you can catch incorrect assignment of null to non-nullable pointer before the assignment itself (instead of detecting it later it's too late). Non-nullable should still be the default because it's faster (no check for null) and safer to use, and I'd trust the optimiser to remove unnecessary checks for null when nullable pointers are casted to non-nullable ones (when you check yourself before casting for instance). -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Feb 14 2009
prev sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Jarrett Billingsley wrote:
 On Sat, Feb 14, 2009 at 8:37 AM, Christopher Wright <dhasenan gmail.com> wrote:
 void Foo(void delegate()? dg) //nullable
 {
    dg();
 }

dozen methods referencing the same nullable member. They're private methods called by public methods that check if the member is null first. I don't want to have to check whether the variable is null every single time I use it; I want to do it once at the start and assume (since it's a single-threaded application and I'm not assigning to that member) that that check works for everything.

....that's the entire point of non-null references.

No. I'm saying this class has a complex code path if some things are not null, and some other code path if they are null. It's quite valid if they are null, but I want to do other things if they aren't, and I don't want to cast constantly.
Feb 14 2009
parent reply "Nick Sabalausky" <a a.a> writes:
"Christopher Wright" <dhasenan gmail.com> wrote in message 
news:gn704j$2s2u$1 digitalmars.com...
 Jarrett Billingsley wrote:
 On Sat, Feb 14, 2009 at 8:37 AM, Christopher Wright <dhasenan gmail.com> 
 wrote:
 void Foo(void delegate()? dg) //nullable
 {
    dg();
 }

dozen methods referencing the same nullable member. They're private methods called by public methods that check if the member is null first. I don't want to have to check whether the variable is null every single time I use it; I want to do it once at the start and assume (since it's a single-threaded application and I'm not assigning to that member) that that check works for everything.

....that's the entire point of non-null references.

No. I'm saying this class has a complex code path if some things are not null, and some other code path if they are null. It's quite valid if they are null, but I want to do other things if they aren't, and I don't want to cast constantly.

You would only need to check when you're going to derefernce or convert to a non-nullable. Nulls could still be stored and passed around without checking. If this doesn't cover what you're concerned about, perhaps you could provide an example?
Feb 14 2009
parent reply Christopher Wright <dhasenan gmail.com> writes:
Nick Sabalausky wrote:
 You would only need to check when you're going to derefernce or convert to a 
 non-nullable. Nulls could still be stored and passed around without 
 checking. If this doesn't cover what you're concerned about, perhaps you 
 could provide an example?

Let's say I originally have this: class Foo { void delegate()? dg; void doStuff() { if (dg) { // some long code path with code duplicated from // other methods in Foo } else { // some other long code path } } } Now I want to refactor that: class Foo { void delegate()? dg; void doStuff() { if (dg) { doPart1; doPart2; } else { // some other long code path } } private void doPart1() { // use dg; why should I check? } private void doPart2() { // use dg; why should I check? } } Small examples don't show it very well.
Feb 14 2009
next sibling parent Christopher Wright <dhasenan gmail.com> writes:
Denis Koroskin wrote:
 private void doPart1()
 {
    void delegate() dg = unchecked(this.dg); // no checking done
    // use dg here without checking
    // *BUT* it someone denies the contract and calls the method without
    // ensuring that this.dg is not null, you'll get a access violation 
 (or NPE)
 }

Fuck no.
Feb 14 2009
prev sibling parent reply "Nick Sabalausky" <a a.a> writes:
"Christopher Wright" <dhasenan gmail.com> wrote in message 
news:gn7jn9$1i0e$1 digitalmars.com...
 class Foo {
     void delegate()? dg;
     void doStuff() {
         if (dg) {
             doPart1;
             doPart2;
         } else {
             // some other long code path
         }
     }

     private void doPart1() {
         // use dg; why should I check?

Because some other function or codepath could still accidentally be breaking your precondition convention, so you're right back to inviting hidden. The extra check prevents that. Also, in many cases (though not all), doPart1() might be better off being generalized like this: private void doPart1(ref void delegate() dg) { // Do stuff } ...or like this: private (void delegate()) doPart1(void delegate() dg) { // Do stuff return dg; }
     }
     private void doPart2() {
         // use dg; why should I check?
     }
 }

 Small examples don't show it very well.

 Denis Koroskin wrote:
 private void doPart1()
 {
    void delegate() dg = unchecked(this.dg); // no checking done
    // use dg here without checking
    // *BUT* it someone denies the contract and calls the method without
    // ensuring that this.dg is not null, you'll get a access violation 
 (or NPE)
 }

Fuck no.

Agreed. Although my argument against it is that if you're going to bother to do that, you may as well just check for null anyway and save yourself the potential runtime error.
Feb 14 2009
next sibling parent "Nick Sabalausky" <a a.a> writes:
"Nick Sabalausky" <a a.a> wrote in message 
news:gn7vef$2nb4$1 digitalmars.com...
 Because some other function or codepath could still accidentally be 
 breaking your precondition convention, so you're right back to inviting 
 hidden. The

Sorry, that should be "...so you're right back to inviting hidden null reference errors."
 extra check prevents that. Also, in many cases (though not all), doPart1() 
 might be better off being generalized like this:

Feb 14 2009
prev sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Nick Sabalausky wrote:
 "Christopher Wright" <dhasenan gmail.com> wrote in message 
 news:gn7jn9$1i0e$1 digitalmars.com...
 class Foo {
     void delegate()? dg;
     void doStuff() {
         if (dg) {
             doPart1;
             doPart2;
         } else {
             // some other long code path
         }
     }

     private void doPart1() {
         // use dg; why should I check?

Because some other function or codepath could still accidentally be breaking your precondition convention, so you're right back to inviting hidden.

Yes, but I'm using nullable variables. I'm happy with getting a hardware exception if I try dereferencing them when they're null. I could pass the non-nullable versions to each of these convenience functions, which works in small cases (one or two such variables) and totally eliminates the value of using member functions. Plus, that's code duplication. As for checking every time... It's code duplication. It's slower than removing the checks, and the checks aren't removed in release mode. The checks are all going to boil down to: if (variable) { } else { assert (false); } It's unnecessary. It's the compiler assuming that the programmer is an idiot and not allowing the programmer to insist that they know what they're doing. I want *warnings* about this. But I think it's going to require far too much boilerplate in complex circumstances.
Feb 15 2009
parent "Nick Sabalausky" <a a.a> writes:
"Christopher Wright" <dhasenan gmail.com> wrote in message 
news:gn96qc$2aic$1 digitalmars.com...
 I want *warnings* about this.

Ok, now *that* I could live with (at least if we ever actually got the option to treat warnings AS warnings, but that's a whole other rant). Personally, I'd still prefer this particular thing to be an error rather than a warning (with an option to unsafe-cast it to a non-null for the rare case where the check is in an inner loop and might actually be a performance issue), but at least now I see your point.
Feb 15 2009
prev sibling parent Christopher Wright <dhasenan gmail.com> writes:
Daniel Keep wrote:
 Both of these syntaxes are solving a problem that doesn't exist.  This
 is why we have null dereference exceptions: accessing a null pointer is
 an error.  All this is doing is moving the onus for the check from the
 hardware to the programmer.

I believe you should be able to use a nullable variable like its non-nullable counterpart. I am uncertain, but perhaps you should be able to implicitly cast to non-nullable, with an assertion added that the variable is not null. This gives you the safety of using non-nullable types and the ease of use of using nullable types.
Feb 14 2009
prev sibling next sibling parent Jarrett Billingsley <jarrett.billingsley gmail.com> writes:
On Fri, Feb 13, 2009 at 10:16 PM, Daniel Keep
<daniel.keep.lists gmail.com> wrote:

 This is (expletive) ridiculous.

 Why is it that I'm unable to criticise even a tiny aspect of someone's
 proposal without people disregarding everything I've said TIME AND AGAIN
 over the past year or more that I'm in support of non-nullable
 references and go "oh but we need non-nullable references"?!

Oh...kay. That was kind of uncalled for. I'm sorry I don't keep tabs on the individual opinions of everyone on this newsgroup. It's particularly difficult to remember things about people when all I can see of them is their name.
Feb 13 2009
prev sibling next sibling parent Jarrett Billingsley <jarrett.billingsley gmail.com> writes:
On Sat, Feb 14, 2009 at 8:37 AM, Christopher Wright <dhasenan gmail.com> wrote:
 void Foo(void delegate()? dg) //nullable
 {
    dg();
 }

You're right for that small example. Now let's say you have an object a dozen methods referencing the same nullable member. They're private methods called by public methods that check if the member is null first. I don't want to have to check whether the variable is null every single time I use it; I want to do it once at the start and assume (since it's a single-threaded application and I'm not assigning to that member) that that check works for everything.

....that's the entire point of non-null references. class Foo { private void delegate() dg; // non-null public this(void delegate()? dg) // nullable { if(dg !is null) this.dg = dg; // hey, dg must be non-null! else throw new Exception("NO! *SLAM*"); } private void method1() { // use dg here, and you're assured that it's non-null // all methods are guaranteed the same thing } } Why on earth would you rather use a nullable reference when you KNOW that it should never be null?
Feb 14 2009
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Sun, 15 Feb 2009 02:22:17 +0300, Christopher Wright <dhasenan gmail.com>
wrote:

 Nick Sabalausky wrote:
 You would only need to check when you're going to derefernce or convert  
 to a non-nullable. Nulls could still be stored and passed around  
 without checking. If this doesn't cover what you're concerned about,  
 perhaps you could provide an example?

Let's say I originally have this: class Foo { void delegate()? dg; void doStuff() { if (dg) { // some long code path with code duplicated from // other methods in Foo } else { // some other long code path } } } Now I want to refactor that: class Foo { void delegate()? dg; void doStuff() { if (dg) { doPart1; doPart2; } else { // some other long code path } } private void doPart1() { // use dg; why should I check? } private void doPart2() { // use dg; why should I check? } } Small examples don't show it very well.

private void doPart1() { void delegate() dg = unchecked(this.dg); // no checking done // use dg here without checking // *BUT* it someone denies the contract and calls the method without // ensuring that this.dg is not null, you'll get a access violation (or NPE) }
Feb 14 2009
prev sibling parent Brian <digitalmars brianguertin.com> writes:
On Tue, 17 Feb 2009 06:03:45 +0300, Sergey Gromov wrote:
 I think it's my favorite design so far.

i second that. this would be wonderful.
Feb 17 2009
prev sibling parent Jarrett Billingsley <jarrett.billingsley gmail.com> writes:
On Fri, Feb 13, 2009 at 5:52 PM, Daniel Keep
<daniel.keep.lists gmail.com> wrote:
 Both of these syntaxes are solving a problem that doesn't exist.  This
 is why we have null dereference exceptions: accessing a null pointer is
 an error.  All this is doing is moving the onus for the check from the
 hardware to the programmer.

 Leave magic out of the language and let the hardware do it's job.  If
 you have a nullable type, it's because you WANT it to be nullable, and
 you shouldn't have to stand on one leg and jump through a burning hoop
 every time you want to look at the damn thing.

Having only nullable references with no non-null counterpart is like a builder building houses with holes in the walls, whether the people who live in them want them or not, so that small animals can get in. Then the residents are forced to check that there are no small animals in their house all the time rather than just _not building the house with holes in the wall to begin with._ The hardware only has a job _to do_ because for decades languages have not been smart enough to prevent invalid pointers to begin with. You could even argue that letting the hardware catch null pointers is an abuse of what the virtual memory hardware is meant for - keeping processes from messing with each others' data, not ensuring program correctness. Ensuring program correctness is the compiler's job.
Feb 13 2009
prev sibling parent reply Sebastian Biallas <groups.5.sepp spamgourmet.com> writes:
Christopher Wright wrote:
 Sebastian Biallas wrote:
 Christopher Wright wrote:
 Oh, and I vote no. I think it's needless complexity. I code without 
 any special care for null objects, and I get a segfault or 
 NullReferenceException maybe once a week, probably less. I've always 
 been able to track down the bug very quickly.

That misses the point completely. It's not about you (as the programmer) getting NPEs; it's about the user getting bug-free software in the first place. In most cases a segfault/NPE is easy to track down for the programmer, I guess nobody would object to this. But once the software is shipped, it's a very serious problem: It's a situation the programmer didn't thought about so you usually have a data loss.

Your language features should get rid of bugs that are easy to make and difficult to track down. It's not worthwhile to alter your language to remove easily found, easily fixed bugs.

Once your software is running, an easy to fix bug is just as bad as an hard to fix bug. It's a bug after all. Of course, you can ask the question whether it's worthwhile to alter the language. Well, for D this is still possible, while other languages will have NPEs till the end of the world.
 Logic errors are difficult to find. Your language cannot help you 
 eliminate those (though contracts and unittests can help you find them). 

"We shouldn't try to avoid bugs of type A because we can't avoid bugs of type B" is a logical fallacy.
 If you do not test your software extensively, you will ship software 
 with logic errors. The testing required to find logic errors will also 
 find low-level errors like segfaults.

Uh.... sorry, but reality proves you wrong. I (and the rest of the world) get NPE in various kinds of extensively tested software. And we can get rid of *all* these errors by a simple language change (A change that should have been done centuries ago). This is a case where you can automatically proof correctness of a specific detail of your software. A chance that shouldn't be ignored because, uh, it's an easy to fix bug (If your rocket to the moon explodes because of an easy to fix bug, well, I don't think that is helps you that it was at least an easy to fix bug). Sebsatian
Feb 11 2009
parent reply Don <nospam nospam.com> writes:
Sebastian Biallas wrote:
 Christopher Wright wrote:
 Sebastian Biallas wrote:
 Christopher Wright wrote:
 Oh, and I vote no. I think it's needless complexity. I code without 
 any special care for null objects, and I get a segfault or 
 NullReferenceException maybe once a week, probably less. I've always 
 been able to track down the bug very quickly.

That misses the point completely. It's not about you (as the programmer) getting NPEs; it's about the user getting bug-free software in the first place. In most cases a segfault/NPE is easy to track down for the programmer, I guess nobody would object to this. But once the software is shipped, it's a very serious problem: It's a situation the programmer didn't thought about so you usually have a data loss.

Your language features should get rid of bugs that are easy to make and difficult to track down. It's not worthwhile to alter your language to remove easily found, easily fixed bugs.

Once your software is running, an easy to fix bug is just as bad as an hard to fix bug. It's a bug after all. Of course, you can ask the question whether it's worthwhile to alter the language. Well, for D this is still possible, while other languages will have NPEs till the end of the world.
 Logic errors are difficult to find. Your language cannot help you 
 eliminate those (though contracts and unittests can help you find them). 

"We shouldn't try to avoid bugs of type A because we can't avoid bugs of type B" is a logical fallacy.
 If you do not test your software extensively, you will ship software 
 with logic errors. The testing required to find logic errors will also 
 find low-level errors like segfaults.

Uh.... sorry, but reality proves you wrong. I (and the rest of the world) get NPE in various kinds of extensively tested software. And we can get rid of *all* these errors by a simple language change (A change that should have been done centuries ago). This is a case where you can automatically proof correctness of a specific detail of your software. A chance that shouldn't be ignored because, uh, it's an easy to fix bug (If your rocket to the moon explodes because of an easy to fix bug, well, I don't think that is helps you that it was at least an easy to fix bug).

Agreed. NPE are normally an easy to fix, easy to diagnose bug, but it can be *extremely* difficult to trigger a case when it occurs. Thus, trigger a run-time assert does not help AT ALL. Only a compile-time check will do.
Feb 11 2009
parent reply grauzone <none example.net> writes:
 Thus, trigger a run-time assert does not help AT ALL. Only a 
 compile-time check will do.

I wouldn't mind if the compiler only inserted assert()s for me. Having to write these assert()s all over _and_ to write the documentation comments, that a parameter must not be null, is the most annoying thing. Doing it at runtime could save us from very annoying compiler behavior, and also simplifies the compiler implementation. (Wasn't D supposed to be simple for the compiler writer? It seems everyone forgot that, even Walter.) By the way, I wouldn't suggest to make non-null the default. This would probably be too big of a language change. Instead, I'd propose to explicitly mark non-nullable reference with "!" (similar to "?").
Feb 12 2009
parent "Nick Sabalausky" <a a.a> writes:
"grauzone" <none example.net> wrote in message 
news:gn18a4$2c9c$1 digitalmars.com...
 Thus, trigger a run-time assert does not help AT ALL. Only a compile-time 
 check will do.

I wouldn't mind if the compiler only inserted assert()s for me. Having to write these assert()s all over _and_ to write the documentation comments, that a parameter must not be null, is the most annoying thing. Doing it at runtime could save us from very annoying compiler behavior, and also simplifies the compiler implementation. (Wasn't D supposed to be simple for the compiler writer? It seems everyone forgot that, even Walter.)

I've been wondering about that too, as I certainly see the value in easy-parsing. However, whenever the ideals of "better error prevention" and "easy parsing" collide, I would much rather see the priority placed on "better error prevention". And I especially don't want to see a potential compile-time error left as a run-time error just for the sake of easier parsing. If easy parsing was my biggest concern, I'd just use an early version of ECMAScript (and I hate ECMAScript, especially the earlier versions).
 By the way, I wouldn't suggest to make non-null the default. This would 
 probably be too big of a language change. Instead, I'd propose to 
 explicitly mark non-nullable reference with "!" (similar to "?").

Another one of the points of D (unlike C++) is to avoid being buried under poor language design choices by not being afraid to break backwords compatibility when it's the only way to fix a problem in the language's design. I'm convinced that D hasn't reached a point where making nun-nullable the default would be too much of a problem.
Feb 12 2009
prev sibling next sibling parent Jarrett Billingsley <jarrett.billingsley gmail.com> writes:
On Wed, Feb 11, 2009 at 3:40 PM, Christopher Wright <dhasenan gmail.com> wrote:
 Your language features should get rid of bugs that are easy to make and
 difficult to track down. It's not worthwhile to alter your language to
 remove easily found, easily fixed bugs.

Except when someone passes a null reference into a large, complex data structure and that reference is passed around blindly and never dereferenced until somewhere far down the line, giving you a segfault in a program location that you never would have expected. By then it's much more difficult to figure out where that null reference came from. I've had a much larger share of these kinds of null reference errors than the "oops someone passed a null into my function and I didn't account for it" kind. Disallowing null references on most public interface functions from the beginning would make those kinds of bugs nonexistent. Otherwise, you have to put explicit runtime null checks on every public interface function, and woe to the guy who forgets to do that. Not to mention you now have to write all these stupid unittests to throw nulls at the code to see if it barfs correctly, rather than it being a simple error caught by the compiler. This reeks of the development problems that dynamic languages have! We have static compilation for a reason; why not take advantage of it? Having null references be the default - nay, the only - option is a useless bit of C arcana that should be dropped. It's a dangerous use of in-band signaling on par with error return codes.
Feb 11 2009
prev sibling next sibling parent Jarrett Billingsley <jarrett.billingsley gmail.com> writes:
On Wed, Feb 11, 2009 at 5:23 PM, Christopher Wright <dhasenan gmail.com> wrote:
 Or add an invariant. You know which field is null when it shouldn't be, so
 that should be simple enough. Or use a property rather than a field and add
 a contract.

 This covers the same cases that non-nullable types do, though admittedly
 with slightly less granularity.

 A complex datastructure might have a complex initialization process that is
 best spread across a few lines of code rather than happening all at once. If
 that is the case, you *can't* use non-nullable types, because that
 restriction isn't valid until the object is fully initialized. But you can
 still use contracts here.

What is your response to the part of my post that was cut off? You know, the part about having to write stupid contracts and how the compiler can and should be checking this kind of thing?
Feb 11 2009
prev sibling next sibling parent Jarrett Billingsley <jarrett.billingsley gmail.com> writes:
On Wed, Feb 11, 2009 at 8:05 PM, Christopher Wright <dhasenan gmail.com> wrote:
 You'll eventually need to convert between nullable(T) and not_nullable(T).
 This requires runtime checks. A struct NotNull(T) could do the same, with
 some significant disadvantages.

And how is that any different than how it is now? Currently you do "if(blah is null) do one thing; else do something else;". This kind of pattern can actually be picked up by the compiler and it would say "hey, blah is null in the first clause but I know it's not null in the second." I think Andrei mentioned this as "flow-dependent typing"? Or something like that. The "if(auto blah = something) ... else ..." pattern also fits very nicely with nullable references.
 But making not_nullable the default, I'm not sure how much work that would
 add for me. I know it would add a fair bit for Walter. Having it as an
 option and not the default would probably make it sufficiently annoying that
 people would not use it, in which case it's no better than adding an
 invariant.

If anything, most of your code would probably just go on working as it has. The few cases where you actually allow null, you'd have to annotate the type, and the compiler would be able to do flow-dependent typing in a vast majority of the cases. In the other cases, where you don't expect null (and forgot to check for it), the compiler will happily point out when you're a moron and pass null to a non-null parameter.
 On the one hand, not_nullable by default would probably do me no good. On
 the other, it would cost me little effort in most cases, most likely, unless
 I got sufficiently annoyed at all the little cases that I gave up and made
 every variable nullable by default.

Except honestly, the number of cases where you _don't_ want something to be null far outweighs the number of cases where you _do_. It's not exactly as insidious as the "throws" clause.
 It would be cool if we could accompany these discussions with sample
 compilers that implement the feature in question. That way, I could speak
 with some certainty when I said that a proposed feature would be annoying.

It would, wouldn't it! That would be nice if we had a nice compiler that we could modify and compile into an EXE. Like DMD. FFS.
Feb 11 2009
prev sibling next sibling parent Brian <digitalmars brianguertin.com> writes:
On Thu, 12 Feb 2009 05:53:10 +0100, Don wrote:
 Thus, trigger a run-time assert does not help AT ALL. Only a
 compile-time check will do.

i couldnt agree more. i think this would be a great addition to D. if the syntax for dealing with nullables is kept simple, i dont see any downside. Foo? f = getFoo(); f.doStuff(); // shouldnt compile if (f) f.doSomething(); // automatically usable
Feb 11 2009
prev sibling next sibling parent reply Brian <digitalmars brianguertin.com> writes:
On Thu, 12 Feb 2009 08:41:54 -0500, Christopher Wright wrote:
 Brian mentioned having to check if the variable is null before using it.
 This would not be easy to implement, and it might be a bit hard to use.
 Again, I'd have to see it in use.

after a bit of thought i dont think theres much/any benefit of forcing a check. if nonnullable was the default then using a nullable version is expected to be unusual and potentially unsafe.
Feb 13 2009
parent reply "Nick Sabalausky" <a a.a> writes:
"Brian" <digitalmars brianguertin.com> wrote in message 
news:gn3dfn$2lp3$1 digitalmars.com...
 On Thu, 12 Feb 2009 08:41:54 -0500, Christopher Wright wrote:
 Brian mentioned having to check if the variable is null before using it.
 This would not be easy to implement, and it might be a bit hard to use.
 Again, I'd have to see it in use.

after a bit of thought i dont think theres much/any benefit of forcing a check. if nonnullable was the default then using a nullable version is expected to be unusual and potentially unsafe.

I don't see much of a point in not forcing a check. I can't think of a case where it would be useful or desirable to use a nullable type without first checking for null (except for passing it to a func that takes a nullable as a param or assigning it to another nullable of the same type, but presumably the check wouldn't be required in those cases). So it may as well be forced (when dereferencing and converting to non-nullable), since you'd never really want/need to do otherwise. Even if the compiler simplified the process by only accepting the exact pattern of "if(x !is null)...", and didn't do any fancier analysis than that, I would still consider that accptable since, as you said, it would be a non-standard use. So therefore, a (very) minor inconvenience like that would be acceptable, particularly considering it would essentially guarantee no null reference errors (aside from manual use of pointers, of course).
Feb 13 2009
parent reply Christopher Wright <dhasenan gmail.com> writes:
Nick Sabalausky wrote:
 "Brian" <digitalmars brianguertin.com> wrote in message 
 news:gn3dfn$2lp3$1 digitalmars.com...
 On Thu, 12 Feb 2009 08:41:54 -0500, Christopher Wright wrote:
 Brian mentioned having to check if the variable is null before using it.
 This would not be easy to implement, and it might be a bit hard to use.
 Again, I'd have to see it in use.

check. if nonnullable was the default then using a nullable version is expected to be unusual and potentially unsafe.

I don't see much of a point in not forcing a check. I can't think of a case where it would be useful or desirable to use a nullable type without first checking for null (except for passing it to a func that takes a nullable as a param or assigning it to another nullable of the same type, but presumably the check wouldn't be required in those cases). So it may as well be forced (when dereferencing and converting to non-nullable), since you'd never really want/need to do otherwise. Even if the compiler simplified the process by only accepting the exact pattern of "if(x !is null)...", and didn't do any fancier analysis than that, I would still consider that accptable since, as you said, it would be a non-standard use. So therefore, a (very) minor inconvenience like that would be acceptable, particularly considering it would essentially guarantee no null reference errors (aside from manual use of pointers, of course).

The primary use case would be: nullable T y = something; non_nullable T x; if (y) x = non_nullable_cast(y); else return; This is a problem if T cannot be allocated by default. I have to do something like: nullable T y = something; if (!y) return; auto x = non_nullable_cast(y); That isn't so bad.
Feb 13 2009
parent reply "Nick Sabalausky" <a a.a> writes:
"Christopher Wright" <dhasenan gmail.com> wrote in message 
news:gn3os2$f6j$3 digitalmars.com...
 Nick Sabalausky wrote:
 "Brian" <digitalmars brianguertin.com> wrote in message 
 news:gn3dfn$2lp3$1 digitalmars.com...
 On Thu, 12 Feb 2009 08:41:54 -0500, Christopher Wright wrote:
 Brian mentioned having to check if the variable is null before using 
 it.
 This would not be easy to implement, and it might be a bit hard to use.
 Again, I'd have to see it in use.

check. if nonnullable was the default then using a nullable version is expected to be unusual and potentially unsafe.

I don't see much of a point in not forcing a check. I can't think of a case where it would be useful or desirable to use a nullable type without first checking for null (except for passing it to a func that takes a nullable as a param or assigning it to another nullable of the same type, but presumably the check wouldn't be required in those cases). So it may as well be forced (when dereferencing and converting to non-nullable), since you'd never really want/need to do otherwise. Even if the compiler simplified the process by only accepting the exact pattern of "if(x !is null)...", and didn't do any fancier analysis than that, I would still consider that accptable since, as you said, it would be a non-standard use. So therefore, a (very) minor inconvenience like that would be acceptable, particularly considering it would essentially guarantee no null reference errors (aside from manual use of pointers, of course).

The primary use case would be: nullable T y = something; non_nullable T x; if (y) x = non_nullable_cast(y); else return; This is a problem if T cannot be allocated by default. I have to do something like: nullable T y = something; if (!y) return; auto x = non_nullable_cast(y); That isn't so bad.

I still like this that someone else mentioned: T? x = something; if(x !is null) { // x is implicitly "T" here, not "T?" } else { // handle null condition (x is still "T?") }
Feb 13 2009
parent reply Christopher Wright <dhasenan gmail.com> writes:
Nick Sabalausky wrote:
 I still like this that someone else mentioned:
 
 T? x = something;
 if(x !is null)
 {
     // x is implicitly "T" here, not "T?"
 }
 else
 {
     // handle null condition (x is still "T?")
 }
 
 

I hate it. It encourages too much nesting, and it silently changes a variable's type.
Feb 14 2009
parent "Nick Sabalausky" <a a.a> writes:
"Christopher Wright" <dhasenan gmail.com> wrote in message 
news:gn6h0r$1mk9$1 digitalmars.com...
 Nick Sabalausky wrote:
 I still like this that someone else mentioned:

 T? x = something;
 if(x !is null)
 {
     // x is implicitly "T" here, not "T?"
 }
 else
 {
     // handle null condition (x is still "T?")
 }

I hate it. It encourages too much nesting, and it silently changes a variable's type.

You're right, I've come to realize that in a different branch of this thread.
Feb 14 2009
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Fri, 13 Feb 2009 21:43:22 +0300, Nick Sabalausky <a a.a> wrote:

 "Christopher Wright" <dhasenan gmail.com> wrote in message
 news:gn3os2$f6j$3 digitalmars.com...
 Nick Sabalausky wrote:
 "Brian" <digitalmars brianguertin.com> wrote in message
 news:gn3dfn$2lp3$1 digitalmars.com...
 On Thu, 12 Feb 2009 08:41:54 -0500, Christopher Wright wrote:
 Brian mentioned having to check if the variable is null before using
 it.
 This would not be easy to implement, and it might be a bit hard to  
 use.
 Again, I'd have to see it in use.

forcing a check. if nonnullable was the default then using a nullable version is expected to be unusual and potentially unsafe.

I don't see much of a point in not forcing a check. I can't think of a case where it would be useful or desirable to use a nullable type without first checking for null (except for passing it to a func that takes a nullable as a param or assigning it to another nullable of the same type, but presumably the check wouldn't be required in those cases). So it may as well be forced (when dereferencing and converting to non-nullable), since you'd never really want/need to do otherwise. Even if the compiler simplified the process by only accepting the exact pattern of "if(x !is null)...", and didn't do any fancier analysis than that, I would still consider that accptable since, as you said, it would be a non-standard use. So therefore, a (very) minor inconvenience like that would be acceptable, particularly considering it would essentially guarantee no null reference errors (aside from manual use of pointers, of course).

The primary use case would be: nullable T y = something; non_nullable T x; if (y) x = non_nullable_cast(y); else return; This is a problem if T cannot be allocated by default. I have to do something like: nullable T y = something; if (!y) return; auto x = non_nullable_cast(y); That isn't so bad.

I still like this that someone else mentioned: T? x = something; if(x !is null) { // x is implicitly "T" here, not "T?" } else { // handle null condition (x is still "T?") }

Or use an existing syntax: Foo? foo = ...; if (Foo value = foo) { // value is a local variable that is only accessible if foo is not null } else { // value is not accessible here }
Feb 13 2009
prev sibling next sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Sat, 14 Feb 2009 11:39:17 +0300, Nick Sabalausky <a a.a> wrote:

 "Michel Fortin" <michel.fortin michelf.com> wrote in message
 news:gn4pl9$1217$1 digitalmars.com...
 On 2009-02-13 14:01:57 -0500, "Nick Sabalausky" <a a.a> said:

 Or use an existing syntax:

 Foo? foo = ...;
 if (Foo value = foo) {
 // value is a local variable that is only accessible if foo is not
 null
 } else {
 // value is not accessible here
 }

I could live with that, but I'd prefer my suggestion because it wouldn't require the creation of an extra label for what's essentially the same variable. With your code we'd just end up with a whole bunch of: Foo? myObj = ...; if (Foo nonnullMyObj = myObj) //etc... // or Foo? nullableMyObj = ...; if (Foo myObj = nullableMyObj) //etc... ...Which just seems unnecessary to me.

Foo? myObj = ...; if (myObj !is null) { doSomethingWith(myObj); myObj = null; // should this be an error? }

-------- // This (a more generalized case of above)... Foo? myObj = ...; if(myObj !is null) { bar1(myObj); if(blah1 > blah2) myObj = null; // Yes, error bar2(); } // ...would change to this... Foo? myObj = ...; bool turnToNull=false; if(myObj !is null) { bar1(myObj); if(blah1 > blah2) turnToNull = true; } if(turnToNull) { myObj = null; bar2(); } --------
 And what about:

 Foo? myObj = ...;
 while (myObj !is null)
     myObj = myObj.next;

-------- Foo? myObj = ...; while(true) { if(myObj.next !is null)

Dang! NullPointerException, because you never checked myObj against null
         myObj = myObj.next;
     else
         break;
 }
 myObj = null; //this line optional

 // Or, if you don't like "while(true)",
 // you could use a "bool done" flag.
 --------

 With Denis's syntax, I'm not sure how the second one would be possible,  

Foo? myObj = ...; if (Foo obj = myObj) { while (true) { if (auto next = obj.next) { obj = next; } else { break; } } }
 but
 the first one would certainly be nicer. So I'm tempted to say that both
 syntaxes should be allowed...

 However, you do have a valid point that my syntax has a problem whenever
 assigning a possibly-null value to the original nullable variable from
 within the scope of the null-check. It can definitely be worked around,  
 but
 I am coming to realize that maybe removing a variable's nullability
 shouldn't happen implicitly unless it can also be added back implicity -
 which would require flow analysis.

 Between that and the difficultly of Denis's syntax handling that simple
 while loop example, I guess full-blown flow-analysis would be needed  
 after
 all in order to realistically eliminate null reference errors on nullable
 types. Dang!

I don't think code flow analysis the way you suggest is useful. I certainly don't want my variable types to be changed at some point after null-check. Code flow analysis would be nice to have, but in a different way: Foo b; // not initialized, can not be read yet if (condition) { b = createFoo(42); } else if (otherCondition) { b = new Foo(); } else { // return b; // error, b is write only return; } // b is both read/write accessible at this point
Feb 14 2009
prev sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Sat, 14 Feb 2009 15:42:57 +0300, Yigal Chripun <yigal100 gmail.com> wrote:

 Denis Koroskin wrote:
 On Sat, 14 Feb 2009 11:39:17 +0300, Nick Sabalausky <a a.a> wrote:

 "Michel Fortin" <michel.fortin michelf.com> wrote in message
 news:gn4pl9$1217$1 digitalmars.com...
 On 2009-02-13 14:01:57 -0500, "Nick Sabalausky" <a a.a> said:

 Or use an existing syntax:

 Foo? foo = ...;
 if (Foo value = foo) {
 // value is a local variable that is only accessible if foo is not
 null
 } else {
 // value is not accessible here
 }

I could live with that, but I'd prefer my suggestion because it wouldn't require the creation of an extra label for what's essentially the same variable. With your code we'd just end up with a whole bunch of: Foo? myObj = ...; if (Foo nonnullMyObj = myObj) //etc... // or Foo? nullableMyObj = ...; if (Foo myObj = nullableMyObj) //etc... ...Which just seems unnecessary to me.

Foo? myObj = ...; if (myObj !is null) { doSomethingWith(myObj); myObj = null; // should this be an error? }

-------- // This (a more generalized case of above)... Foo? myObj = ...; if(myObj !is null) { bar1(myObj); if(blah1 > blah2) myObj = null; // Yes, error bar2(); } // ...would change to this... Foo? myObj = ...; bool turnToNull=false; if(myObj !is null) { bar1(myObj); if(blah1 > blah2) turnToNull = true; } if(turnToNull) { myObj = null; bar2(); } --------
 And what about:

 Foo? myObj = ...;
 while (myObj !is null)
 myObj = myObj.next;

-------- Foo? myObj = ...; while(true) { if(myObj.next !is null)

Dang! NullPointerException, because you never checked myObj against null
 myObj = myObj.next;
 else
 break;
 }
 myObj = null; //this line optional

 // Or, if you don't like "while(true)",
 // you could use a "bool done" flag.
 --------

 With Denis's syntax, I'm not sure how the second one would be possible,

Foo? myObj = ...; if (Foo obj = myObj) { while (true) { if (auto next = obj.next) { obj = next; } else { break; } } }
 but
 the first one would certainly be nicer. So I'm tempted to say that both
 syntaxes should be allowed...

 However, you do have a valid point that my syntax has a problem  
 whenever
 assigning a possibly-null value to the original nullable variable from
 within the scope of the null-check. It can definitely be worked
 around, but
 I am coming to realize that maybe removing a variable's nullability
 shouldn't happen implicitly unless it can also be added back implicity  
 -
 which would require flow analysis.

 Between that and the difficultly of Denis's syntax handling that simple
 while loop example, I guess full-blown flow-analysis would be needed
 after
 all in order to realistically eliminate null reference errors on  
 nullable
 types. Dang!

I don't think code flow analysis the way you suggest is useful. I certainly don't want my variable types to be changed at some point after null-check. Code flow analysis would be nice to have, but in a different way: Foo b; // not initialized, can not be read yet if (condition) { b = createFoo(42); } else if (otherCondition) { b = new Foo(); } else { // return b; // error, b is write only return; } // b is both read/write accessible at this point

can't this be done with: Foo b = condition ? createFoo(42) : (otherCondition ? new Foo() : return); // use b

It solves the simplest examples, but not more complex ones, so it is not generic enough.
Feb 14 2009
prev sibling next sibling parent reply Brian <digitalmars brianguertin.com> writes:
On Mon, 09 Feb 2009 04:19:55 +0000, Brian wrote:

 On Mon, 09 Feb 2009 04:25:55 +0300, Denis Koroskin wrote:
 
 So, let's ask the community: Would you like to see nullable types in D?
 
 http://www.micropoll.com/akira/mpview/539369-138652 (please, don't
 abuse by voting multiple time)
 
 Explain your reasoning in newsgroups. Thank you.

i vote yes, i would absolutely love non-nullable types. in some cases i even use dummy objects to avoid null checks.

well, at the very least id like to see a (optional) warning/error for uninitialized pointers. This way I can be certain there are absolutely zero null pointers unless the null keyword itself is used. A quick search for 'null' would be guaranteed to show all possibly null variables. I know I'm not the first to wish for such. (Does a warning exist yet that I don't know about?)
Feb 09 2009
parent Brian <digitalmars brianguertin.com> writes:
On Mon, 09 Feb 2009 18:20:56 +0000, Brian wrote:
 well, at the very least id like to see a (optional) warning/error for
 uninitialized pointers. This way I can be certain there are absolutely
 zero null pointers unless the null keyword itself is used. A quick
 search for 'null' would be guaranteed to show all possibly null
 variables. I know I'm not the first to wish for such. (Does a warning
 exist yet that I don't know about?)

just to clarify, this is what id like. given this code: void main() { Foo f; bar(null); } class Foo() {} void bar(Foo) {} $ dmd main.d Completed Successfully $ dmd -wnull main.d main.d(2): warning: variable 'f' is default initialized to null main.d(3): warning: passing null as argument to function expecting Foo To prevent the warnings, you would need to change main to this: void main() { Foo f = null; bar(cast(Foo)null); }
Feb 09 2009
prev sibling next sibling parent Jarrett Billingsley <jarrett.billingsley gmail.com> writes:
On Fri, Feb 13, 2009 at 3:23 PM, Nick Sabalausky <a a.a> wrote:
 Another thing to think about is delegates. Even if classes are non-null by
 default, we can still get the same old null reference problem with
 delegates:

 class Foo
 {
    void delegate() dg;
    void callDg()
    {
        dg();  // Oops! Forgot to check for null!!
    }
 }

 void main()
 {
    auto f = new Foo();
    f.callDg();
 }

 This could probably be solved by whatever mechanism is used for classes.

I was thinking nullability would be applicable to all reference types. A null pointer, delegate, array, or whatever is just as bad as a null reference.
Feb 13 2009
prev sibling parent Jarrett Billingsley <jarrett.billingsley gmail.com> writes:
On Tue, Feb 17, 2009 at 10:36 PM, Michel Fortin
<michel.fortin michelf.com> wrote:
 On 2009-02-17 18:17:55 -0500, Christopher Wright <dhasenan gmail.com> said:

 One possible change: implicit casting with an assertion that the nullable
 value is not null. I'm not sure whether this is a good idea. On the one
 hand, it's easier for the programmer to use nullable types in that case; on
 the other, it encourages people not to have error handling.

I think it's a good idea: good enough to be useful, simple enough to be implemented without much hassle. Once we have enough code using non-nullable, it'll be easier to evaluate the impacts of adding compile-time constrains for nullables, and whether it's worth it or not. I wouldn't make it just like an assertion though. I'd make it something separate you can deactiave with a compiler switch, just like bound checking.

Actually each little debug check has a separate internal flag in the compiler. It just doesn't expose them all at that level of granularity. It just lets you turn them all on or all off. :P
Feb 17 2009