December 14, 2018
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



December 15, 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

December 15, 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.
December 15, 2018
On Sat, 15 Dec 2018 03:01:18 +0000, Rubn wrote:
> On Saturday, 15 December 2018 at 02:14:15 UTC, Jonathan M Davis wrote:
>> 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.

You don't know that cast(Object)null is an invalid value. You need to check whether you currently have a valid value. So you create a wrapper struct that has a sentry value. You know for certain that that sentry value doesn't conflict with anything the user might want to use (unlike, say, T.init).

This lets you organize your code in a nicer way when you have complex initialization, or when you need to de-initialize a value.

I mentioned it in the named arguments thread as a way to check whether all required parameters to a function were provided via an argument struct. Whether explicitly passing null is valid or not depends on the function, and the Invoker wrapper can't make that call. Whether an argument must be provided depends on the function signature alone. So the Invoker wrapper could use Nullable to distinguish between an argument that is absent and an argument that is present and equal to its type's .init value.
December 14, 2018
On Friday, December 14, 2018 8:41:49 PM MST Neia Neutuladh via Digitalmars-d wrote:
> On Sat, 15 Dec 2018 03:01:18 +0000, Rubn wrote:
> > On Saturday, 15 December 2018 at 02:14:15 UTC, Jonathan M Davis wrote:
> >> 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.
>
> You don't know that cast(Object)null is an invalid value. You need to check whether you currently have a valid value. So you create a wrapper struct that has a sentry value. You know for certain that that sentry value doesn't conflict with anything the user might want to use (unlike, say, T.init).
>
> This lets you organize your code in a nicer way when you have complex initialization, or when you need to de-initialize a value.
>
> I mentioned it in the named arguments thread as a way to check whether all required parameters to a function were provided via an argument struct. Whether explicitly passing null is valid or not depends on the function, and the Invoker wrapper can't make that call. Whether an argument must be provided depends on the function signature alone. So the Invoker wrapper could use Nullable to distinguish between an argument that is absent and an argument that is present and equal to its type's .init value.

Exactly. Right now, generic code can be written that is able to use Nullable to store a value and treat null as a valid value. Plenty of code does treat null as an invalid value, but not all code does, and in that situation, there is a difference between null and not having a value. You can write generic code that does not care one whit what type it's operating on that uses Nullable, whereas if Nullable treated pointers differently, that code would have to be special-cased for pointers in able to function properly.

Having Nullable treat pointers differently results in a type that's essentially worthless for anything other than value types (including generic code). If the code isn't generic, then Nullable!(T*) is then the same as T* but with different syntax, making it pointless. And if the code is generic, then you end up with subtle bugs (and potentially crashes) due to Nullable behaving differently for different types.

- Jonathan M Davis



December 14, 2018
On Friday, December 14, 2018 8:26:17 PM MST Rubn via Digitalmars-d wrote:
> 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.

Changing the behavior of Nullable would silently break existing code. So, regardless of whether Nullable's current behavior is desirable, simply changing its behavior is completely unacceptable.

And if we were designing Nullable from scratch, trying to make the names involved clearer in the process, then it would probably just be something like Optional, with isEmpty or hasValue being the function use to check whether it contained anything rather than isNull. Either way, as I keep pointing out, for it to be possible to reliably use it in generic code, it needs its current semantics, and having it treat nullable types differently makes it pointless to use it outside of generic code. As such, having a Nullable/Optional type which treats pointers differently just makes no sense.

But if you want that, as I understand it, you can find at least one library on code.dlang.org that has defined its own Optional type which works that way.

- Jonathan M Davis



December 15, 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);

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.

>
> 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.

Cheers,
- Ali

December 15, 2018
On Saturday, 15 December 2018 at 04:06:55 UTC, Jonathan M Davis wrote:
> On Friday, December 14, 2018 8:26:17 PM MST Rubn via Digitalmars-d wrote:
>> 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.
>
> Changing the behavior of Nullable would silently break existing code. So, regardless of whether Nullable's current behavior is desirable, simply changing its behavior is completely unacceptable.
>
> And if we were designing Nullable from scratch, trying to make the names involved clearer in the process, then it would probably just be something like Optional, with isEmpty or hasValue being the function use to check whether it contained anything rather than isNull. Either way, as I keep pointing out, for it to be possible to reliably use it in generic code, it needs its current semantics, and having it treat nullable types differently makes it pointless to use it outside of generic code. As such, having a Nullable/Optional type which treats pointers differently just makes no sense.
>
> But if you want that, as I understand it, you can find at least one library on code.dlang.org that has defined its own Optional type which works that way.
>
> - Jonathan M Davis

I'm not arguing that Optional isn't a useful type, but if you are going to fix the problems with deprecating the "alias this get;" then you are going to be breaking just as much code. You might as well deprecate the entire class and add one that is called Optional. And you feel such a class called Nullable that does what the name suggestions isn't useful. Then don't readd the type once it has been deprecated and removed.
December 15, 2018
On Saturday, December 15, 2018 5:33:04 AM MST Rubn via Digitalmars-d wrote:
> On Saturday, 15 December 2018 at 04:06:55 UTC, Jonathan M Davis
>
> wrote:
> > On Friday, December 14, 2018 8:26:17 PM MST Rubn via
> >
> > Digitalmars-d wrote:
> >> 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.
> >
> > Changing the behavior of Nullable would silently break existing code. So, regardless of whether Nullable's current behavior is desirable, simply changing its behavior is completely unacceptable.
> >
> > And if we were designing Nullable from scratch, trying to make the names involved clearer in the process, then it would probably just be something like Optional, with isEmpty or hasValue being the function use to check whether it contained anything rather than isNull. Either way, as I keep pointing out, for it to be possible to reliably use it in generic code, it needs its current semantics, and having it treat nullable types differently makes it pointless to use it outside of generic code. As such, having a Nullable/Optional type which treats pointers differently just makes no sense.
> >
> > But if you want that, as I understand it, you can find at least one library on code.dlang.org that has defined its own Optional type which works that way.
> >
> > - Jonathan M Davis
>
> I'm not arguing that Optional isn't a useful type, but if you are going to fix the problems with deprecating the "alias this get;" then you are going to be breaking just as much code. You might as well deprecate the entire class and add one that is called Optional. And you feel such a class called Nullable that does what the name suggestions isn't useful. Then don't readd the type once it has been deprecated and removed.

Plenty of code uses Nullable without using the alias and would not be broken by the alias being removed, but it's not a sure thing that the alias is going to be deprecated anyway.

- Jonathan M Davis



December 15, 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)
{
    ...
    if(member.isNull)
        member = t;
    bar(member.get);
    ...
}

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