On 19.11.2017 05:04, Walter Bright wrote:
On 11/18/2017 6:25 PM, Timon Gehr wrote:
I.e., baseClass should have type Nullable!ClassDeclaration. This does
not in any form imply that ClassDeclaration itself needs to have a
null value.
Converting back and forth between the two types doesn't sound appealing.
...
I can't see the problem. You go from nullable to non-nullable by
checking for null, and the other direction happens implicitly.
What should the default initializer for a type do?
There should be none for non-nullable types.
I suspect you'd wind up needing to create an "empty" object just to
satisfy that requirement. Such as for arrays of objects, or objects with
a cyclic graph.
...
Again, just use a nullable reference if you need null. The C# language
change makes the type system strictly more expressive. There is nothing
that cannot be done after the change that was possible before, it's just
that the language allows to document and verify intent better.
Interestingly, `int` isn't nullable, and we routinely use rather ugly
hacks to fake it being nullable, like reserving a bit pattern like 0, -1
or 0xDEADBEEF and calling it INVALID_VALUE, or carrying around some
other separate flag that says if it is valid or not. These are often
rich sources of bugs.
As you can guess, I happen to like null, because there are no hidden
bugs from pretending it is a valid value - you get an immediate program
halt - rather than subtly corrupted results.
...
Making null explicit in the type system is compatible with liking null.
(In fact, it is an endorsement of null. There are other options to
accommodate optional values in your language.)
Yes, my own code has produced seg faults from erroneously assuming a
value was not null. But it wouldn't have been better with non-nullable
types, since the logic error would have been hidden
It was your own design decision to hide the error. This is not something
that a null-aware type system promotes, and I doubt this is what you
would be promoting if mainstream type systems had gone that route earlier.
and may have been
much, much harder to recognize and track down.
No, it would have been better because you would have been used to the
more explicit system from the start and you would have just written
essentially the same code with a few more compiler checks in those cases
where they apply, and perhaps you would have suffered a handful fewer
null dereferences. Being able to document intent across programmers in a
compiler-checked way is also useful, even if one manages to remember all
assumptions that are valid about one's own code. Note that the set of
valid assumptions may change as the code base evolves.
The point of types is to classify values into categories such that types
in the same category support the same operations. It is not very clean
to have a special null value in all those types that does not support
any of the operations that references are supposed to support.
Decoupling the two concepts into references an optionality gets rid of
this issue, cleaning up both concepts.
I wish there was a null
for int types.
AFAIU, C# will now have 'int?'.
At least we sort of have one for char types, 0xFF. And
there's that lovely NaN for floating point! Too bad it's woefully
underused.
...
It can also be pretty annoying. It really depends on the use case. Also
this is in direct contradiction with your earlier points. NaNs don't
usually blow up.
I found this out when testing my DFA (data flow analysis) algorithms.
void test(int i) {
int* p = null;
if (i) p = &i;
...
if (i) *p = 3;
...
}
Note that the code is correct, but DFA says the *p could be a null
dereference. (Replace (i) with any complex condition that there's no
way the DFA can prove always produces the same result the second time.)
Yes, there is a way. Put in an assertion. Of course, at that point you
are giving up, but this is not the common case.
An assertion can work, but doesn't it seem odd to require adding a
runtime check in order to get the code to compile?
...
Not really. The runtime check is otherwise just implicit in every
pointer dereference (though here there happens to be hardware support
for that check).
(This is subtly different from the current use of assert(0) to flag
unreachable code.)
...
It's adding a runtime check in order to get the code to compile. ;)
Also, you can often just write the code in a way that the DFA will
understand. We are doing this all the time in statically-typed
programming languages.
I didn't invent this case. I found it in real code; it happens often
enough. The cases are usually much more complex, I just posted the
simplest reduction. I was not in a position to tell the customer to
restructure his code, though :-)
I don't doubt that this happens. I'm just saying that often enough it
does not. (Especially if the check is in the compiler.)
I'm not fighting for explicit nullable in D by the way. I'm mostly
trying to dispel wrong notions of what it is.