Current sentiment on Nullable.get

Rubn where at is.this
Sat Dec 15 03:26:17 UTC 2018


On Saturday, 15 December 2018 at 02:14:15 UTC, Jonathan M Davis 
wrote:
> On Friday, December 14, 2018 5:05:51 PM MST Rubn via 
> Digitalmars-d wrote:
>> On Friday, 14 December 2018 at 23:08:30 UTC, Jonathan M Davis
>
>> So for this naturally nullable type, would you say it has a 
>> value?
>>
>> int* ptr = null;
>
> It has the value of null, which can either have a distinct 
> meaning unto itself, or it could mean that the pointer doesn't 
> contain a value. Which it is depends on what the code is doing. 
> For example, IIRC, SQL treats a value that is NULL and a value 
> which doesn't exist as two separate states. Plenty of other 
> code would consider null to be an invalid value.
>
> Without Nullable, if you wanted null to indicate that there is 
> no value, then T* represents that just fine, whereas if you 
> wanted null to be a valid value, then you'd need T** so that 
> whether the outer pointer was null could be used to indicate 
> whether a value was present. With an Optional or Nullable type, 
> it's then possible to replace the T** with Nullable!(T*) in the 
> case where you consider null to be a valid value, and if you 
> considered null to be invalid, then either Nullable!T or T* 
> would work, though T* would require a heap allocation.
>
>> > n = value;
>> > return n.get;
>> >
>> > could then actually fail the assertion in get if the code 
>> > was instantiated with a type that was naturally nullable. 
>> > The current approach - regardless of the name of Nullable - 
>> > prevents that problem.
>>
>> When would you ever want to use get() if you know isNull is 
>> true
>> (including for if isNull() returned true for pointers that 
>> have a
>> null value) ?
>
> 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);
>
> is guaranteed to work. So, don't need to ever call isNull in 
> any code where you have assigned the Nullable a value, whereas 
> if the value can affect isNull, then calling get without 
> calling isNull risks failing the assertion in get in generic 
> code, forcing you to check isNull even when you know that you 
> assigned it a value. The code could even be operating on value 
> types in most case, but it would still have to check isNull in 
> case it was instantiated with a pointer. And it would be easy 
> to write code in such a way that work perfectly fine with value 
> types but then blew up in your face later when someone used a 
> pointer with it, whereas if Nullable treats pointers the same 
> as value types, then that's not a problem.
>
>> > Having a Nullable treat pointers differently from value 
>> > types would completely defeat the purpose of having Nullable 
>> > work with pointers in the first place, and if you're not 
>> > writing generic code, a Nullable that doesn't have a 
>> > separate bool indicating whether it has a value or not is 
>> > pointless for pointers, because it would just be creating a 
>> > wrapper that did nothing that pointers don't do naturally.
>>
>> Are you really saying it's pointless after the you spent a 
>> paragraph writing up about how it's useful to have 1 type 
>> provide the same interface ?
>
> I'm saying that if Nullable's isNull is affected by the value 
> of the pointer it contains, then it's pointless to use 
> Nullable!(T*) in non-generic code, because the Nullable!(T*) is 
> acting exactly like a T*, whereas right now, Nullable!(T*) acts 
> more like T** but with the benefit of not having to allocate a 
> place on the heap to contain the T*.
>
> And if Nullable's isNull is affected by the value of the 
> pointer it contains, then it doesn't work well in generic code, 
> because the Nullable will act differently depending on the type 
> that it contains, forcing you to either call isNull in places 
> where it would be unnecessary for types which aren't nullable, 
> and possibly forcing you to special-case the code when 
> operating on nullable types so that the code works the same 
> regardless of which type it's given.
>
> As such, it makes no sense for Nullable's isNull to be affected 
> by the value of what it contains. It would make more sense for 
> Nullable to disallow pointers and other nullable types than for 
> it to treat them differently from non-nullable types.
>
> 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.
>
>> You keep saying this, but the very first version of Nullable 
>> could in fact contain pointers.
>>
>> https://github.com/dlang/phobos/commit/0c142994d9b5cb9f379eca28f3a625c7493 70e4a#diff-4e008aedb3026d4a84f58323e53bf017R873
>>
>> https://run.dlang.io/is/bu0hqt
>>
>> Maybe there was a Nullable type in D1, but I can't find one at 
>> least.
>
> I recall it being the case that D2's Nullable did not work with 
> pointers and classes and that someone changed it so that it did 
> so that it would work in generic code. If that's not true, then 
> I misremembered, and it's less reasonable that it has the name 
> that it does.
>
> Either way, I'm quite certain that the main motivation behind 
> introducing Nullable was so that you could have value types be 
> nullable without allocating on the heap. If pointers and 
> classes were not explicitly prohibited, then it was presumably 
> because the original author (Andrei IIRC) did not think that 
> prohibiting it was worthwhile. And while it may not have been 
> the purpose behind Nullable to use it with nullable types, it 
> is true that it's useful with them in situations where you need 
> to know when a variable has been given a value or not, and the 
> code considers null to be a valid value.
>
> Another situation where it's popular to use Nullable with 
> non-nullable types is dynamic arrays, because some array 
> operations treat null the same as empty, making it so that some 
> folks consider it to be too risky (or just plain bad practice) 
> to actually use null as a special value for dynamic arrays, 
> whereas Nullable!(int[]) does not have that problem, because it 
> does not care about what null means for dynamic arrays.
>
> Regardless, having Nullable treat nullable types differently 
> from value types would make it so that it no longer worked well 
> in generic code, and it would make it pointless to use outside 
> of generic code.
>
>> Don't have to rename it, can just make it do what it actually 
>> is named after.
>
> That would break existing code, and it would mean that Nullable 
> had terrible semantics due to the issues that I explained above.
>
> - Jonathan M Davis

As a side note you could also easily add a separate function that 
would do what you are suggesting:

bool hasValue() const;

which wouldn't check if the value is null (for naturally nullable 
types). Now, tell me, what would you name the function to check 
if the value is null, including for naturally nullable types if 
you wanted to add it to the current Nullable struct ? What would 
you name that function to make it obvious to a read at first 
glance that that is how it functions.


More information about the Digitalmars-d mailing list