Current sentiment on Nullable.get

Rubn where at is.this
Sat Dec 15 03:01:18 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);

Why wouldn't you just do

foo( value );

then ? If you know the value isn't going to be null then you 
shouldn't be using nullable.

> 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

The generic code you showed doesn't happen. If you know your 
value isn't going to be null, then you don't need to use the 
nullable type. This generic code you provided doesn't happen in 
production code. The more common case is you have a class/pointer 
that is null, your generic code ends up looking like this as 
result:

if( !n.isNull() ) {
     static if( isPointer!T || isClass!T || isEtc!T ) {
         if(T t = n.get()) {
             // use t
         }
     }
     else {
         n.get();
     }
}

// can become:

if( !n.isNull() ) {
     n.get(); // use value
}

Where as for your generic code:

Nullable!T n = value;
foo(n.get());

// becomes:

foo( value ); // no use for nullable if we can guarantee a value 
in it


If you do have code that uses T values and T* arguable this is 
more useful:

Nullable!T n = value;

if( !n.isNull() ) {
     // will always be true for int
     // will only be true for int* if it isn't null
     foo( n.get() );
}

This is arguably better for generic code, if you want what you 
are suggesting, then you shouldn't be using Nullable at all.


     foo( value ); // ok if we pass null here



More information about the Digitalmars-d mailing list