Current sentiment on Nullable.get
Jonathan M Davis
newsgroup.d at jmdavisprog.com
Sat Dec 15 15:44:25 UTC 2018
On Saturday, December 15, 2018 2:11:37 AM MST aliak via Digitalmars-d wrote:
> On Saturday, 15 December 2018 at 02:14:15 UTC, Jonathan M Davis
> > The problem I'm pointing out is that if isNull cares about
> > whether the value the Nullable contains is null, then you can
> > no longer rely on the fact that a Nullable has been assigned a
> > value to determine whether get is legal to call (at least not
> > in generic code). You would always have to call isNull first,
> > because the value it was assigned might have been null if that
> > code had been instantiated with a type that can be null.
> > Currently, something like
> > n = value;
> > foo(n.get);
> How is this code realistic? Why would anyone assign a value to a
> Nullable inside a scope where they can see the code and then pass
> it as get to a function. And if you were getting the Nullable
> from a different scope then why would you ever call get without
> checking isNull first. The only place code would break is if
> people considered null a valid value with operational semantics
> for T*. This is also something I think is valid usage because
> like you say, Nullable!(T*) make zero sense otherwise. But it
> make zero sense for Nullable!Class to treat null as a valid
> value. Contrary to what you are saying, it makes generic code
> MORE complicated.
> In generic code if I get a Nullable!T* I know I need to check
> whatever get returns is null or not. With any other T which could
> be a reference type it complicates my code.
You're assuming that the code is going to be calling a member function on
the pointer or class reference or do something else that would dereference
it. It's quite possible for the code to simply be passing it around. And if
you want a simple case where it would make sense to call get on a Nullable
after assigning it a value, then consider a struct that has member that's a
Nullable. A member function could then have code like
auto foo(T t)
member = t;
If T were a class or pointer, and t were null, then that code would fail the
assertion in get if nullable types were treated differently. But with how
Nullable currently works, if the code isn't doing anything that would result
in the value being dereferenced, then everything works perfectly fine.
And if the code is actually going to be doing something that would
dereference the value, then odds are that null isn't a valid value anyway.
Certainly, it doesn't usually make sense to have generic code checking for
null, because then the code isn't generic. I don't think that I have ever
seen a generic piece of code special-cased to check pointers for null. And
honestly, I would consider it a major code smell if generic code were
checking if a pointer were null unless it was always operating on pointers
and was just generic with regards to what was pointed to.
On the other hand, any code that is generically passing values around and
uses Nullable is going to have serious problems if Nullable treats pointers
differently whenever it's given a value that's null.
So, I suppose that an argument could be made that in the case where null is
not a valid value, Nullable could treat a null value as the same as not
having been given a value, but in any situation where null is a valid value,
that does not work at all. And if the code is not generic, having Nullable
use a separate bool can be extremely useful, whereas having it treat null
the same as not having a value would make Nullable useless for pointers,
because its semantics would be the same as if you used the pointer directly.
I really don't see a good argument here for Nullable having different
semantics than it currently does. There's certainly an argument to be made
that it should have been called something other than Nullable, but its
current semantics are very useful, whereas having it treat pointers
differently really wouldn't be.
> > On the other hand, right now, because Nullable works the same
> > regardless of what type it containts, it works quite well in
> > generic code, and because Nullable!(T*) basically gives you T**
> > without the extra heap allocation, there are use cases where it
> > makes sense to use Nullable!(T*) even though T* is already
> > nullable. So, ultimately, the way that Nullable currently works
> > makes sense, whereas having isNull be affected by the value
> > contained by the Nullable does not. It could certainly be
> > argued that the naming is poor, but the semantics are exactly
> > what they should be.
> I think I understand what the problem is now. You keep on
> interchanging Nullable!(T*) with just plain Nullable. And if we
> are only talking about Nullable!(T*) then I can agree with what
> you say. But generic code is not just T*'s and when it's not T*s
> then it makes zero sense for isNull to return false if the value
> is a null ref type.
Nullable!(T*) is basically the same as T**. All of this arguing about trying
to make Nullable treat pointers differently is like arguing that T** is
useless, because T* can already be null. That extra level of indirection
means something and can have real value. In the cases where it doesn't, you
just don't use that extra level of indirection, but making it so that that
extra level of indirection isn't possible reduces what you can do.
Regardless, it's not like we're going to change Nullable's semantics. That
would silently break existing code. The only way that something would change
would be if it were decided that Nullable were so broken that it had to be
replaced, and I have a hard time believing that you could get Andrei to
agree with that (and he's ultimately the one who would have to be
convinced). Either way, I would argue very strongly against any attempt to
remove Nullable. It would break a lot of code that's perfectly fine as-is.
- Jonathan M Davis
More information about the Digitalmars-d