www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Current sentiment on Nullable.get

reply FeepingCreature <feepingcreature gmail.com> writes:
Having recently been reminded that `alias Nullable.get this` 
exists, I'm considering a pull request to deprecate it. What's 
the sentiment on `alias Nullable.get this` in the community? My 
unchanged stance is that it's a blight and an interminable source 
of impossible to find runtime bugs, and an anti-feature that 
misses the point of Nullable to provide safe optional types.

The typical problem goes like this:

1. A dependency switches from T to Nullable!T.
2. You update your dependencies.
3. Your program still compiles (because Nullable!T silently casts 
to "T or exception") and you notice nothing.
4. Sometime later, your program crashes in production.

Thoughts?
Dec 10 2018
next sibling parent Jacob Carlborg <doob me.com> writes:
On 2018-12-10 11:01, FeepingCreature wrote:
 Having recently been reminded that `alias Nullable.get this` exists, I'm 
 considering a pull request to deprecate it. What's the sentiment on 
 `alias Nullable.get this` in the community? My unchanged stance is that 
 it's a blight and an interminable source of impossible to find runtime 
 bugs, and an anti-feature that misses the point of Nullable to provide 
 safe optional types.
I agree that `alias Nullable.get this` should be deprecated. I think the whole Nullable should be deprecated and replaced with Optional. -- /Jacob Carlborg
Dec 10 2018
prev sibling next sibling parent reply aliak <something something.com> writes:
On Monday, 10 December 2018 at 10:01:45 UTC, FeepingCreature 
wrote:
 Having recently been reminded that `alias Nullable.get this` 
 exists, I'm considering a pull request to deprecate it. What's 
 the sentiment on `alias Nullable.get this` in the community? My 
 unchanged stance is that it's a blight and an interminable 
 source of impossible to find runtime bugs, and an anti-feature 
 that misses the point of Nullable to provide safe optional 
 types.

 The typical problem goes like this:

 1. A dependency switches from T to Nullable!T.
 2. You update your dependencies.
 3. Your program still compiles (because Nullable!T silently 
 casts to "T or exception") and you notice nothing.
 4. Sometime later, your program crashes in production.

 Thoughts?
Sounds like a step forward, deprecating that. Do people consider Nullable a type of Optional thing in general? I have a written a bit more details here [0] but basically I don't think deprecating .get is enough to make it "safe" - granted - depending on what safe means. In the context of D a segfault is safe so maybe that's ok. But then deprecating get doesn't add safety in that context anyway. And, I've never really considered Nullable as something that provides Optional semantics. But maybe that was it's intention? [0] https://github.com/aliak00/optional/blob/03e0dc0594f1f19274389227e67dd3fba00a9d3f/README.md#what-about-stdtypeconsnullable-and-stdrangeonly
Dec 10 2018
next sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
On Monday, 10 December 2018 at 15:47:53 UTC, aliak wrote:
 [snip]

 Sounds like a step forward, deprecating that.

 Do people consider Nullable a type of Optional thing in 
 general? I have a written a bit more details here [0] but 
 basically I don't think deprecating .get is enough to make it 
 "safe" - granted - depending on what safe means. In the context 
 of D a segfault is safe so maybe that's ok. But then 
 deprecating get doesn't add safety in that context anyway. And, 
 I've never really considered Nullable as something that 
 provides Optional semantics. But maybe that was it's intention?

 [0] 
 https://github.com/aliak00/optional/blob/03e0dc0594f1f19274389227e67dd3fba00a9d3f/README.md#what-about-stdtypeconsnullable-and-stdrangeonly
Does it make sense to deprecate Nullable as a whole, rather than just that piece? Why would I use Nullable when I can use your optional library?
Dec 10 2018
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Dec 10, 2018 at 05:07:10PM +0000, jmh530 via Digitalmars-d wrote:
 On Monday, 10 December 2018 at 15:47:53 UTC, aliak wrote:
 [snip]
 
 Sounds like a step forward, deprecating that.
 
 Do people consider Nullable a type of Optional thing in general? I
 have a written a bit more details here [0] but basically I don't
 think deprecating .get is enough to make it "safe" - granted -
 depending on what safe means. In the context of D a segfault is safe
 so maybe that's ok. But then deprecating get doesn't add safety in
 that context anyway.  And, I've never really considered Nullable as
 something that provides Optional semantics. But maybe that was it's
 intention?
 
 [0] https://github.com/aliak00/optional/blob/03e0dc0594f1f19274389227e67dd3fba00a9d3f/README.md#what-about-stdtypeconsnullable-and-stdrangeonly
Does it make sense to deprecate Nullable as a whole, rather than just that piece? Why would I use Nullable when I can use your optional library?
AFAIK, Nullable was intended for giving an extra "null" value to by-value POD types. Outside of that narrow use case, it's really not a very nice API at all, and the implementation is a bit simplistic, dating from an earlier time when we didn't have as many attributes / new features as we do today. As a result, there are some very nasty corner cases where it behaves in counterintuitive ways that can't be fixed without breaking existing code in evil ways. Its name is also a bit misleading, esp. when you apply it to by-reference types. There have been various attempts to give it a facelift, but so far none has made it through. I'm inclined to agree with deprecating it, except I dread the long painful process of getting a replacement added to Phobos. T -- Freedom: (n.) Man's self-given right to be enslaved by his own depravity.
Dec 10 2018
prev sibling next sibling parent Jacob Carlborg <doob me.com> writes:
On 2018-12-10 18:07, jmh530 wrote:

 Does it make sense to deprecate Nullable as a whole, rather than just
 that piece?
Yes. -- /Jacob Carlborg
Dec 10 2018
prev sibling parent reply aliak <something something.com> writes:
On Monday, 10 December 2018 at 17:07:10 UTC, jmh530 wrote:
 On Monday, 10 December 2018 at 15:47:53 UTC, aliak wrote:
 [...]
Does it make sense to deprecate Nullable as a whole, rather than just that piece? Why would I use Nullable when I can use your optional library?
I would be all for that. I guess the only reason would be to have pointer semantics for value types 🤷‍♂️And I'm not really sure why you'd want that anyway.
Dec 11 2018
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Dec 11, 2018 at 08:44:03AM +0000, aliak via Digitalmars-d wrote:
 On Monday, 10 December 2018 at 17:07:10 UTC, jmh530 wrote:
 On Monday, 10 December 2018 at 15:47:53 UTC, aliak wrote:
 [...]
Does it make sense to deprecate Nullable as a whole, rather than just that piece? Why would I use Nullable when I can use your optional library?
I would be all for that. I guess the only reason would be to have pointer semantics for value types 🤷‍♂️And I'm not really sure why you'd want that anyway.
It's useful for representing a value that isn't there. E.g., if I have a struct with some int fields parsed from some config file, say, I'd like to be able to distinguish between a field that's actually set, vs. one that isn't specified in the config file. T -- English is useful because it is a mess. Since English is a mess, it maps well onto the problem space, which is also a mess, which we call reality. Similarly, Perl was designed to be a mess, though in the nicest of all possible ways. -- Larry Wall
Dec 11 2018
parent reply aliak <something something.com> writes:
On Tuesday, 11 December 2018 at 18:04:37 UTC, H. S. Teoh wrote:
 On Tue, Dec 11, 2018 at 08:44:03AM +0000, aliak via 
 Digitalmars-d wrote:
 On Monday, 10 December 2018 at 17:07:10 UTC, jmh530 wrote:
 On Monday, 10 December 2018 at 15:47:53 UTC, aliak wrote:
 [...]
Does it make sense to deprecate Nullable as a whole, rather than just that piece? Why would I use Nullable when I can use your optional library?
I would be all for that. I guess the only reason would be to have pointer semantics for value types 🤷‍♂️And I'm not really sure why you'd want that anyway.
It's useful for representing a value that isn't there. E.g., if I have a struct with some int fields parsed from some config file, say, I'd like to be able to distinguish between a field that's actually set, vs. one that isn't specified in the config file. T
True! Sorry, I meant that as a response to "why would i use Nullable over the mentioned Optional library". In that case optional represents existence, and then the only thing nullable has over that is pointer semantics. But I think pointer semantics is a poor representation of the existence of a value. What's the difference between using Nullable!int and int* to see if it's set in terms of intent/semantics/safety? (I realize allocation would be a difference). Cheers, - Ali
Dec 11 2018
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Tuesday, December 11, 2018 2:48:36 PM MST aliak via Digitalmars-d wrote:
 On Tuesday, 11 December 2018 at 18:04:37 UTC, H. S. Teoh wrote:
 On Tue, Dec 11, 2018 at 08:44:03AM +0000, aliak via

 Digitalmars-d wrote:
 On Monday, 10 December 2018 at 17:07:10 UTC, jmh530 wrote:
 On Monday, 10 December 2018 at 15:47:53 UTC, aliak wrote:
 [...]
Does it make sense to deprecate Nullable as a whole, rather than just that piece? Why would I use Nullable when I can use your optional library?
I would be all for that. I guess the only reason would be to have pointer semantics for value types 🤷‍♂️And I'm not really sure why you'd want that anyway.
It's useful for representing a value that isn't there. E.g., if I have a struct with some int fields parsed from some config file, say, I'd like to be able to distinguish between a field that's actually set, vs. one that isn't specified in the config file. T
True! Sorry, I meant that as a response to "why would i use Nullable over the mentioned Optional library". In that case optional represents existence, and then the only thing nullable has over that is pointer semantics. But I think pointer semantics is a poor representation of the existence of a value. What's the difference between using Nullable!int and int* to see if it's set in terms of intent/semantics/safety? (I realize allocation would be a difference).
Ultimately, allocation is the main difference here. Nullable provides a way to emulate the behavior of a pointer with regards to nullability without having to allocate on the heap. An Optional or Maybe type is ultimately the same thing, just with a different name. If it weren't for the issue of heap allocation, it could easily argued that pointers negate the need for any kind of Nullable/Optional/Maybe type, because they provide that functionality. And they don't even cause memory safety issues if you're not doing pointer arithmetic. So, really, I think that the need for heap allocation is _exactly_ the issue here that these types are designed to solve and that without that, a Nullable/Optional/Maybe type isn't adding much. At most, it's making it clear that it's expected that the value can be null/empty/missing, because not all functions that involve pointers consider null to be an acceptable value. And IMHO, the arguments about Nullable vs Optional vs Maybe etc. are mostly nonsense in that it's really just an argument about how the type is named. And while names matter, any of those names work just fine, and it's ultimately very subjective. It's like arguing about whether strip or trim is a better function name for a function that removes whitespace from the end of strings. Either name works just fine. The difference is pretty much just a difference in preference or expectation based on what the programmer has seen other languages or libraries do. It's entirely subjective. IMHO, the only real naming issue that we have with Nullable is that once it was changed to allow actual pointers (whereas originally, it just contained types that could not themselves be null), the property isNull became confusing to some folks, because they thought that the null value of a pointer and whether the Nullable itself was null were related, when they're not (and if they were, it would cause subtle bugs - especially in generic code). So, based on that, it could be argued that Nullable is a worse name, but originally, since it didn't even allow types that were themselves nullable, that was a non-issue, and at this point, we don't change the names of things in Phobos just because we decide that another name is better, because Walter and Andrei don't think that that's worth the code breakage. To change or replace Nullable, we'd need a reason other than some folks wanting a different name in order to change it. The alias issue is arguably worth a deprecation given how code that uses a Nullable really should be written with the idea that it could be null rather than having an actual value, and having the alias makes it easier to introduce bugs related to that, but that doesn't require replacing Nullable or changing how it fundamentally works. It just means deprecating the alias. Nullable itself can continue to function the same way, and plenty of code using Nullable would not need to be changed due to deprecating the alias. If someone prefers some other implementation of what Nullable is doing, they're free to use that instead, but most of the arguments around replacing Nullable seem to either revolve around the fact that some folks don't like the name or the fact that they think that a Nullable containing a type that can be null should use whether the value is null to determine whether the Nullable is null/empty instead of using a separate bool, and IMHO, doing so would just be a source of bugs. Also, at least some existing code relies on that functionality (e.g. it's been mentioned before that vibe.d relies on it). - Jonathan M Davis
Dec 11 2018
next sibling parent reply aliak <something something.com> writes:
On Tuesday, 11 December 2018 at 22:32:45 UTC, Jonathan M Davis 
wrote:
 Ultimately, allocation is the main difference here. Nullable 
 provides a way to emulate the behavior of a pointer with 
 regards to nullability without having to allocate on the heap. 
 An Optional or Maybe type is ultimately the same thing, just 
 with a different name. If it weren't for the issue of heap 
 allocation, it could easily argued that pointers negate the 
 need for any kind of Nullable/Optional/Maybe type, because they 
 provide that functionality. And they don't even cause memory 
 safety issues if you're not doing pointer arithmetic. So, 
 really, I think that the need for heap allocation is _exactly_ 
 the issue here that these types are designed to solve and that 
 without that, a Nullable/Optional/Maybe type isn't adding much.
This is not the point of optional types. They're just there to denote if a value exists or not. The fact that you can use D's Nullable to avoid allocating an int* is a happy proxy effect at best and only applies to a subset of types T can take on. And now I've heard three reasons for the purpose of Nullable: * It's an optional/maybe type * It's to give value types pointer semantics * And now, it's to avoid heap allocations for value types applies to value types - and that makes sense. But the way it's used in D is just a pothole of confusion and WTFs (see below).
 At most, it's making it clear that it's expected that the value 
 can be null/empty/missing, because not all functions that 
 involve pointers consider null to be an acceptable value.
That makes a big difference. With everything. Writing the code, maintaining code, reviewing code, coming back to code, getting in to a language. Principle of least surprises. Intuitiveness. These all matter: class C {} struct S {} * What is Nullable!(int*)? An int * that can be null? But wait... * What is Nullable!C? A reference type that can be null? Isn't that just "C" ? What is this adding? How does this make sense? * What does this do: "Nullable!C a = null; a.isNull;"? if it's false does that mean C exists? So C exists? * What is Nullable!S? A struct that can be null or a struct that may exist? Or was it just to avoid heap allocation? * What's the difference between null and any of those other types' isNull? * Nullable!C a = null; a.get; // should that throw/assert? These are all non-obvious.
 And IMHO, the arguments about Nullable vs Optional vs Maybe 
 etc. are mostly nonsense in that it's really just an argument 
 about how the type is named. And while names matter, any of 
 those names work just fine, and it's ultimately very 
 subjective. It's like arguing about whether strip or trim is a 
 better function name for a function that removes whitespace 
 from the end of strings. Either name works just fine. The 
 difference is pretty much just a difference in preference or 
 expectation based on what the programmer has seen other 
 languages or libraries do. It's entirely subjective.
It can be subjective, but there are also objectively better names and better APIs. And IMO we shouldn't settle for "fine" if we don't have to. Right now, I can accurately say this about Nullable: Nullable is a type that can be null, but may not be null even if assigned null. Is it subjective that that's good or bad? I personally think it's objectively bad.
 IMHO, the only real naming issue that we have with Nullable is 
 that once it was changed to allow actual pointers (whereas 
 originally, it just contained types that could not themselves 
 be null), the property isNull became confusing to some folks, 
 because they thought that the null value of a pointer and 
 whether the Nullable itself was null were related, when they're 
 not (and if they were, it would cause subtle bugs - especially 
 in generic code).
Yes, and this is just bad. Bad bad bad. How can any API that causes subtle bugs be something anyone would want to keep around?? Cheers, - Ali
Dec 13 2018
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, December 13, 2018 4:41:01 AM MST aliak via Digitalmars-d wrote:
 On Tuesday, 11 December 2018 at 22:32:45 UTC, Jonathan M Davis

 wrote:
 Ultimately, allocation is the main difference here. Nullable
 provides a way to emulate the behavior of a pointer with
 regards to nullability without having to allocate on the heap.
 An Optional or Maybe type is ultimately the same thing, just
 with a different name. If it weren't for the issue of heap
 allocation, it could easily argued that pointers negate the
 need for any kind of Nullable/Optional/Maybe type, because they
 provide that functionality. And they don't even cause memory
 safety issues if you're not doing pointer arithmetic. So,
 really, I think that the need for heap allocation is _exactly_
 the issue here that these types are designed to solve and that
 without that, a Nullable/Optional/Maybe type isn't adding much.
This is not the point of optional types. They're just there to denote if a value exists or not.
Which you can do with a pointer. There is no need to have an "optional" type if you don't care about heap allocations. If you don't care about heap allocations, than you can just use T* and not bother with creating a library type. It even works if you already have a pointer, because you can have a pointer to a pointer: T**. Having an optional/maybe type allows you to indicate whether a value is present _without_ that extra heap allocation. It gives you pointer semantics with regards to whether a value is present without needing a pointer. So, ultimately, avoiding a heap allocation is the reason that such optional/maybe types exist, and it's why Nullable exists.
 At most, it's making it clear that it's expected that the value
 can be null/empty/missing, because not all functions that
 involve pointers consider null to be an acceptable value.
That makes a big difference. With everything. Writing the code, maintaining code, reviewing code, coming back to code, getting in to a language. Principle of least surprises. Intuitiveness. These all matter: class C {} struct S {} * What is Nullable!(int*)? An int * that can be null? But wait... * What is Nullable!C? A reference type that can be null? Isn't that just "C" ? What is this adding? How does this make sense? * What does this do: "Nullable!C a = null; a.isNull;"? if it's false does that mean C exists? So C exists? * What is Nullable!S? A struct that can be null or a struct that may exist? Or was it just to avoid heap allocation? * What's the difference between null and any of those other types' isNull? * Nullable!C a = null; a.get; // should that throw/assert? These are all non-obvious.
The behavior is completely obvious if you understand that whether a Nullable is null refers to the Nullable itself and not the value that it contains. Looking at the documentation, it does need to be improved so that that is clearer. But if you understand that, then there is no confusion. And if you're not writing generic code, then the only reason to even put a pointer or reference in a Nullable in the first place is so that you can differentiate between a pointer or reference that has been given a value rather than being simply default-initialized. If you didn't care about that, then you would just use the pointer or reference directly, because the Nullable would do _nothing_ for you. It would be a pointless wrapper. And if you're writing generic code, then having isNull act differently for pointers than it does for value types would not work at all, because you'd have to special-case the code to handle the fact that assigning the Nullable a value could still result in isNull being true in the case where the Nullable contained a pointer or reference. So, the code would no longer be generic. And if it wasn't going to be generic, then you'd just use the naked pointer or reference rather than using a Nullable. The entire reason that Nullable was changed to allow types that are naturally nullable was so that generic code could use Nullable without having to be special-cased for pointers or references. That does come with the downside that isNull might be confusing if you don't understand what Nullable is, and the documentation clearly needs to be improved on that front, but there's no problem there with how Nullable works. It's just now a worse name, because it was changed to allow types that are naturally nullable and that can therefore have the value of null.
 IMHO, the only real naming issue that we have with Nullable is
 that once it was changed to allow actual pointers (whereas
 originally, it just contained types that could not themselves
 be null), the property isNull became confusing to some folks,
 because they thought that the null value of a pointer and
 whether the Nullable itself was null were related, when they're
 not (and if they were, it would cause subtle bugs - especially
 in generic code).
Yes, and this is just bad. Bad bad bad. How can any API that causes subtle bugs be something anyone would want to keep around??
How on earth does Nullable cause subtle bugs? The only issue with it that I see is that the fact that it's aliased to its get member risks bugs when you change code that used a T to use Nullable!T. Without that alias, you'd be forced to update the code and make sure that it could handle when isNull was true, whereas right now, it can be trivial to just change a variable's type from T to Nullable!T and assume that it works, and even if you're careful and try to make the code use get and isNull properly, you can easily miss a place where the alias is used and end up with a bug. But if the alias is removed like the OP wants, then that entire problem goes away, and then I don't see why Nullable would cause subtle bugs. I'll grant you that now that Nullable allows types which are naturally nullable, the name is worse, because it does sometimes cause confusion around isNull. But it's against current policy to rename stuff in Phobos simply to give it a better name, and if the documentation is improved, that problem should largely go away. At that point, the only folks who would be confused are those that don't read the docs, and folks who don't read the documentation for the code they're reading or maintaining are going to be confused in general. If we were to start over, then yeah, it should probably be named Optional or Maybe or whatever rather than Nullable given that it accepts naturally nullable types, but I fail to see how its design causes bugs aside from the problems caused by the alias. And we should be able to fix that via deprecation, only breaking code that arguably should be broken in the process, whereas renaming stuff breaks _all_ code using Nullable, the vast majority of which is perfectly fine - which is why Walter and Andrei are against allowing renaming stuff. It breaks tons of code, and in general, the benefit is highly subjective if not outright negligible. It's probably worth more in this case than in many other cases, but it would still involve breaking tons of perfectly valid code. - Jonathan M Davis
Dec 13 2018
next sibling parent aliak <something something.com> writes:
On Thursday, 13 December 2018 at 13:47:34 UTC, Jonathan M Davis 
wrote:
 Which you can do with a pointer. There is no need to have an 
 "optional" type if you don't care about heap allocations. If 
 you don't care about heap allocations, than you can just use T* 
 and not bother with creating a library type. It even works if 
 you already have a pointer, because you can have a pointer to a 
 pointer: T**. Having an optional/maybe type allows you to 
 indicate whether a value is present _without_ that extra heap 
 allocation. It gives you pointer semantics with regards to 
 whether a value is present without needing a pointer. So, 
 ultimately, avoiding a heap allocation is the reason that such 
 optional/maybe types exist, and it's why Nullable exists.
Maybe it's why Nullable in D exists for *some* types, but it is certainly not why optional types exist -> https://en.wikipedia.org/wiki/Option_type. Why would you use Nullable!Class, or Nullable!(T*)? It's not to avoid allocation. Now whether or not you think a pointer is good enough is a different argument. If you are in the camp where a pointer is good enough then that's fine but I completely disagree with a pointer being good enough to represent the absence of a value. That's a hack with pointers. Just like you can use functions to model object oriented programming, it's a hack. In the case of pointers it only works if you consider null as not being part of a pointer's value domain. If you think null is a valid value of a pointer, then you're stuck. Also, "if (!null) then do something" is becoming more recognized as an anti pattern these days, and having pointers represent existence just helps you litter your code with null checks. Checking for null becomes an unenforcible requirement for stability. Instead, with a properly designed optional type, this requirement becomes checked and/or forced.
 The behavior is completely obvious if you understand that 
 whether a Nullable is null refers to the Nullable itself and 
 not the value that it contains. Looking at the documentation, 
 it does need to be improved so that that is clearer. But if you 
 understand that, then there is no confusion.
The behavior of many things becomes completely obvious once you understand a set of things, generally speaking. The brain power needed to understand that is unnecessarily more than what would be required with a better/different API.
 And if you're not writing generic code, then the only reason to 
 even put a pointer or reference in a Nullable in the first 
 place is so that you can differentiate between a pointer or 
 reference that has been given a value rather than being simply 
 default-initialized. If you didn't care about that, then you 
 would just use the pointer or reference directly, because the 
 Nullable would do _nothing_ for you. It would be a pointless 
 wrapper.
The Nullable implementation does indeed make it a pointless wrapper. Which is the problem. It actually make it worse. Because with a pointer or reference, if you check is null, then you know you can use it, but with Nullable, checking isNull doesn't give you any guaranteers.
 And if you're writing generic code, then having isNull act 
 differently for pointers than it does for value types would not 
 work at all, because you'd have to special-case the code to 
 handle the fact that assigning the Nullable a value could still 
 result in isNull being true in the case where the Nullable 
 contained a pointer or reference. So, the code would no longer 
 be generic. And if it wasn't going to be generic, then you'd
Why would you want to use a pointer or reference that is null? Right now you have to do this: void f(T)(Nullable!T a) { if (!a.isNull) { static if (is(T == class) || isPointer!T || is(T == interface)) { if (a.get !is null) { // can use a } } else { // can you a } } }
 just use the naked pointer or reference rather than using a 
 Nullable. The entire reason that Nullable was changed to allow 
 types that are naturally nullable was so that generic code 
 could use Nullable without having to be special-cased for 
 pointers or references. That does come with the downside that 
 isNull might be confusing if you don't understand what Nullable 
 is, and the documentation clearly needs to be improved on that 
 front, but there's no problem there with how Nullable works. 
 It's just now a worse name, because it was changed to allow 
 types that are naturally nullable and that can therefore have 
 the value of null.

 IMHO, the only real naming issue that we have with Nullable 
 is that once it was changed to allow actual pointers 
 (whereas originally, it just contained types that could not 
 themselves be null), the property isNull became confusing to 
 some folks, because they thought that the null value of a 
 pointer and whether the Nullable itself was null were 
 related, when they're not (and if they were, it would cause 
 subtle bugs - especially in generic code).
Yes, and this is just bad. Bad bad bad. How can any API that causes subtle bugs be something anyone would want to keep around??
How on earth does Nullable cause subtle bugs?
Au. I misread your sentence as saying Nullable causes subtle bugs :o Sorry. Cheers, - Ali
Dec 14 2018
prev sibling next sibling parent reply Rubn <where is.this> writes:
On Thursday, 13 December 2018 at 13:47:34 UTC, Jonathan M Davis 
wrote:
 The behavior is completely obvious if you understand that 
 whether a Nullable is null refers to the Nullable itself and 
 not the value that it contains. Looking at the documentation, 
 it does need to be improved so that that is clearer. But if you 
 understand that, then there is no confusion.
Isn't the behavior of everything obvious if you know how it works? The reasons things should be named the way they are is to help identify what they do. What you are describing is more easily understood as a class named "Optional". You mention that the template didn't work with types that were already nullable. I want to know what the individual was thinking when they expanded the functionality to include nullable types. They could have easily added extra code that does an additional check to see if the types are null. That's the logical thing to do for a class named "Nullable". The way it is currently implemented is the logical way to implement "Optional". You wouldn't store a float point number in a type called int, would you ?
Dec 14 2018
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Friday, December 14, 2018 3:23:57 PM MST Rubn via Digitalmars-d wrote:
 On Thursday, 13 December 2018 at 13:47:34 UTC, Jonathan M Davis

 wrote:
 The behavior is completely obvious if you understand that
 whether a Nullable is null refers to the Nullable itself and
 not the value that it contains. Looking at the documentation,
 it does need to be improved so that that is clearer. But if you
 understand that, then there is no confusion.
Isn't the behavior of everything obvious if you know how it works? The reasons things should be named the way they are is to help identify what they do. What you are describing is more easily understood as a class named "Optional". You mention that the template didn't work with types that were already nullable. I want to know what the individual was thinking when they expanded the functionality to include nullable types. They could have easily added extra code that does an additional check to see if the types are null. That's the logical thing to do for a class named "Nullable". The way it is currently implemented is the logical way to implement "Optional". You wouldn't store a float point number in a type called int, would you ?
As I already explained in multiple posts here, the entire reason that Nullable was expanded to work with pointers and classes was so that it would work in generic code. So, it's now possible to have a templated function or type use Nullable without caring about what type it actually contains. It's just whatever type the template was instantiated with, whereas previously it would have had to do stuff like static if(is(Nullable!T)) n.nullify() else n = null; and static if(is(Nullable!T)) return n.get; else return *n; in order to work with both naturally nullable types and those that need Nullable to be "null" in a single piece of templated code. Now, you can just do n.nullify(); and return n.get; regardless of whether the Nullable contains a naturally nullable type or not. If Nullable were to treat pointers differently so that isNull were true if the value were null instead of using a separate bool, then you'd have to special case Nullable in generic code again, because you could no longer rely on assigning a value to Nullable meaning that it had a value. 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. 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. As for the name, Nullable was a perfectly fine name when it couldn't contain pointers. Is it a great name now that it can contain pointers? No, and the documentation should probably be improved to make it even clearer that Nullable has nothing to do with the value of the type it contains, just about whether the Nullable itself is "null," but the way that Nullable works is the way that it should work - regardless of its name. If we were adding Nullable now, we would almost certainly give it a different name, but it's against Phobos policy to simply rename stuff, because it breaks everything that uses the symbol when you do that. If the current design caused bugs, then we might replace it, because we'd be fixing and preventing bugs in the process, but it doesn't. If anything, its current design prevents bugs by ensuring that Nullable functions the same regardless of what type it contains, making it work well in generic code, whereas having Nullable treat pointers differently would create bugs in generic code. The problem with Nullable's design that does cause bugs is the fact that it's aliased to its get member - which is what this thread was originally about - and the way to fix that is to deprecate the alias, thereby only breaking code that uses the alias. There's no need to break _all_ code that uses Nullable, which is exactly what would happen if we were to rename it - which is why we don't rename things in Phobos anymore just because it's thought that the name isn't as good as it could be. If we decide that we need to replace something, because its design is subtoptimal, and we can't fix it in place, then we can end up with a new name, because we're actually adding something new to replace something broken, not simply renaming it. If this were 2010, then yeah, we'd probably rename it (e.g. a number of years ago, I fixed a bunch of the names in std.string, because they didn't follow Phobos' naming conventions), but Walter and Andrei no longer consider renaming a construct to give it a better name worth the breakage; there was already tons of complaining about that when we did it when we had a much smaller user base. It would be much worse now. So, if there's a fix that we're going to do at this point, it's to improve Nullable's documentation, not to rename it. - Jonathan M Davis
Dec 14 2018
parent reply Rubn <where is.this> writes:
On Friday, 14 December 2018 at 23:08:30 UTC, Jonathan M Davis 
wrote:
 On Friday, December 14, 2018 3:23:57 PM MST Rubn via 
 Digitalmars-d wrote:
 On Thursday, 13 December 2018 at 13:47:34 UTC, Jonathan M Davis

 wrote:
 The behavior is completely obvious if you understand that 
 whether a Nullable is null refers to the Nullable itself and 
 not the value that it contains. Looking at the 
 documentation, it does need to be improved so that that is 
 clearer. But if you understand that, then there is no 
 confusion.
Isn't the behavior of everything obvious if you know how it works? The reasons things should be named the way they are is to help identify what they do. What you are describing is more easily understood as a class named "Optional". You mention that the template didn't work with types that were already nullable. I want to know what the individual was thinking when they expanded the functionality to include nullable types. They could have easily added extra code that does an additional check to see if the types are null. That's the logical thing to do for a class named "Nullable". The way it is currently implemented is the logical way to implement "Optional". You wouldn't store a float point number in a type called int, would you ?
As I already explained in multiple posts here, the entire reason that Nullable was expanded to work with pointers and classes was so that it would work in generic code. So, it's now possible to have a templated function or type use Nullable without caring about what type it actually contains. It's just whatever type the template was instantiated with, whereas previously it would have had to do stuff like static if(is(Nullable!T)) n.nullify() else n = null; and static if(is(Nullable!T)) return n.get; else return *n; in order to work with both naturally nullable types and those that need Nullable to be "null" in a single piece of templated code. Now, you can just do n.nullify(); and return n.get; regardless of whether the Nullable contains a naturally nullable type or not. If Nullable were to treat pointers differently so that isNull were true if the value were null instead of using a separate bool, then you'd have to special case Nullable in generic code again, because you could no longer rely on assigning a value to Nullable meaning that it had a value.
So for this naturally nullable type, would you say it has a value? int* ptr = null;
 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) ?
 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 ? struct SomeType { void foo(); } test( Nullable!SomeType() ); test( Nullable!(SomeType*) ); void test(T)(Nullable!T value) { if( !value.isNull() ) { value.get().foo() } }
 As for the name, Nullable was a perfectly fine name when it 
 couldn't contain pointers. Is it a great name now that it can 
 contain pointers? No, and the documentation should probably be 
 improved to make it even clearer that Nullable has nothing to 
 do with the value of the type it contains, just about whether 
 the Nullable itself is "null," but the way that Nullable works 
 is the way that it should work - regardless of its name. If we 
 were adding Nullable now, we would almost certainly give it a 
 different name, but it's against Phobos policy to simply rename 
 stuff, because it breaks everything that uses the symbol when 
 you do that. If the current design caused bugs, then we might 
 replace it, because we'd be fixing and preventing bugs in the 
 process, but it doesn't. If anything, its current design 
 prevents bugs by ensuring that Nullable functions the same 
 regardless of what type it contains, making it work well in 
 generic code, whereas having Nullable treat pointers 
 differently would create bugs in generic code.
You keep saying this, but the very first version of Nullable could in fact contain pointers. https://github.com/dlang/phobos/commit/0c142994d9b5cb9f379eca28f3a625c749370e4a#diff-4e008aedb3026d4a84f58323e53bf017R873 https://run.dlang.io/is/bu0hqt Maybe there was a Nullable type in D1, but I can't find one at least.
 The problem with Nullable's design that does cause bugs is the 
 fact that it's aliased to its get member - which is what this 
 thread was originally about - and the way to fix that is to 
 deprecate the alias, thereby only breaking code that uses the 
 alias. There's no need to break _all_ code that uses Nullable, 
 which is exactly what would happen if we were to rename it - 
 which is why we don't rename things in Phobos anymore just 
 because it's thought that the name isn't as good as it could be.
I don't think you would break that much code if you made isNull() return true if the value is nullable and is actually null. Any code that is checking isNull() would also have to be checking if the pointer is null. Then only use the pointer if it's optional value is not null.
 If we decide that we need to replace something, because its 
 design is subtoptimal, and we can't fix it in place, then we 
 can end up with a new name, because we're actually adding 
 something new to replace something broken, not simply renaming 
 it. If this were 2010, then yeah, we'd probably rename it (e.g. 
 a number of years ago, I fixed a bunch of the names in 
 std.string, because they didn't follow Phobos' naming 
 conventions), but Walter and Andrei no longer consider renaming 
 a construct to give it a better name worth the breakage; there 
 was already tons of complaining about that when we did it when 
 we had a much smaller user base. It would be much worse now.

 So, if there's a fix that we're going to do at this point, it's 
 to improve Nullable's documentation, not to rename it.

 - Jonathan M Davis
Don't have to rename it, can just make it do what it actually is named after.
Dec 14 2018
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Dec 14 2018
next sibling parent reply Rubn <where is.this> writes:
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
Dec 14 2018
parent reply Neia Neutuladh <neia ikeran.org> writes:
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.
Dec 14 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Dec 14 2018
prev sibling next sibling parent reply Rubn <where is.this> writes:
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.
Dec 14 2018
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Dec 14 2018
parent reply Rubn <where is.this> writes:
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.
Dec 15 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Dec 15 2018
prev sibling parent reply aliak <something something.com> writes:
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
Dec 15 2018
next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Dec 15 2018
parent reply aliak <something something.com> writes:
On Saturday, 15 December 2018 at 15:44:25 UTC, Jonathan M Davis 
wrote:
 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.
Yes. You're accessing get without checking isNull first. Bad code, bad practice, should not pass code review. Not sure I see how that's a bad thing. Sounds like you want a type that's called HasBeenAssignTo. Not an optional type. Where is this code that accesses get without checking isNull first? I've never seen it and it just sounds like bad code.
 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.
What serious problems? Asserting when not checking isNull before calling .get? Hardly a serious problem.
 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.
And how is null a valid value for a class?
 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.
Not arguing to change T*. Like I already said I agree with you when it comes to raw pointers. Anyway, it's fine if Nullable doesn't change. It is what it is, which is a wrapper type that tells you if something was assigned to. It's usage as an optional is quite lacking though. No bind function, and even if there was it'd have to special case if because Nullable!Class = null is a valid value. You can't map it because it's not a range. You're left with all the same holes as using raw pointers to check for existence with no compile time enforceable checks. And you have to special case your Nullable if you want to call functions on the T that Nullable is wrapping. Which is actually a Much bigger use case for optionals (i.e. optionals that wrap actual types that you want to perform operations on). Cheers, - Ali
Dec 16 2018
parent reply Sebastiaan Koppe <mail skoppe.eu> writes:
On Sunday, 16 December 2018 at 23:13:32 UTC, aliak wrote:
 On Saturday, 15 December 2018 at 15:44:25 UTC, Jonathan M Davis 
 wrote:
 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.
Yes. You're accessing get without checking isNull first. Bad code, bad practice, should not pass code review. Not sure I see how that's a bad thing.
Agreed. Normally one would do either: member.orElse(t).map!(bar); // or member.withDefault(t).map!(bar);
 Sounds like you want a type that's called HasBeenAssignTo. Not 
 an optional type.
Yep, that seems to be the source of our differences. My idea of Nullable has always been that it is exactly like Optional, but it is not. It might be a good idea to update the docs to reflect that.
Dec 17 2018
parent reply Sebastiaan Koppe <mail skoppe.eu> writes:
Oops...

When creating a PR to update the docs on Nullable, I was checking 
the semantics of Scala's optional type.

 From what I can gather there was a lot of discussion in the Scala 
community about the very same topic:

---
var a: Option[String] = Some(null)
a.isDefined   // returns true!
---

They settled for this because Scala is build on top of Java and 
code written in Java could use the null value to signify 
something other than None. Often something like "present, but 
uninitialised".

It is something that they very much like to see different, and a 
lot of Scala developers cringe when they see it. Also, they have 
the Option constructor that corresponds with the semantics aliak 
and I argued for:

---
var a: Option[String] = Option(null)
a.isDefined    // return false!
---

I am slowly understanding why you would need some version of 
Nullable where isNull needs to return true for null values. It is 
for that uncommon case where null has semantics beyond not being 
a value. For instance, a library could use null to signify that a 
key is present but has no values assigned to it. Or some code 
could assign a difference between an array with a null item and 
an empty array.

---
[null].head() == Nullable!T(null);
[].head() == Nullable!T.init;
---

Thanks for your perseverance Jonathan :)
Dec 17 2018
parent aliak <something something.com> writes:
On Monday, 17 December 2018 at 09:39:12 UTC, Sebastiaan Koppe 
wrote:
 Oops...

 When creating a PR to update the docs on Nullable, I was 
 checking the semantics of Scala's optional type.

 From what I can gather there was a lot of discussion in the 
 Scala community about the very same topic:

 ---
 var a: Option[String] = Some(null)
 a.isDefined   // returns true!
 ---

 They settled for this because Scala is build on top of Java and 
 code written in Java could use the null value to signify 
 something other than None. Often something like "present, but 
 uninitialised".

 It is something that they very much like to see different, and 
 a lot of Scala developers cringe when they see it. Also, they 
 have the Option constructor that corresponds with the semantics 
 aliak and I argued for:

 ---
 var a: Option[String] = Option(null)
 a.isDefined    // return false!
 ---

 I am slowly understanding why you would need some version of 
 Nullable where isNull needs to return true for null values. It 
 is for that uncommon case where null has semantics beyond not 
 being a value. For instance, a library could use null to 
 signify that a key is present but has no values assigned to it. 
 Or some code could assign a difference between an array with a 
 null item and an empty array.

 ---
 [null].head() == Nullable!T(null);
 [].head() == Nullable!T.init;
 ---

 Thanks for your perseverance Jonathan :)
Yeah, that scala thing :( - you can guess which side of the argument I'm on :p Java null mixed in with scala optionals has bitten me numerous times - e.g. trying to get JSON data out of jackson. Most crashes have been because of java to scala and/or kotlin interop and because of java nulls. There was one SDK we were working on - kotlin version and swift version. Guess which one basically didn't crash, and guess which one crashed because of java nulls hiding in kotlin nullables. I remember at some point swift allowed the same thing. And again, it was just a source of bugs. I don't think you can do that now in swift unless you really mess with reflection APIs and the objective-c runtime. But I'm not sure. Anyway, I do agree that you need a way to represent null if it's a valid value. And IMO, null is a valid pointer value which is why I also argue you can't use pointers as optionals since null can be a valid value. And the cool thing about D is that we have pointers and classes. And I think an optional of T* should be sufficient for the uncommon use-cases you speak of (see: https://run.dlang.io/is/qh2OdQ). I'm still not sure why you would want that with D classes 🤷‍♂️ But maybe there's code out there that does, which would be great to look at to see why it is doing that. Cheers, - Ali
Dec 17 2018
prev sibling parent Neia Neutuladh <neia ikeran.org> writes:
On Sat, 15 Dec 2018 09:11:37 +0000, aliak wrote:
 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 use Nullable!(T*) only when you need to distinguish "a null value that has been set" from "no value because it has not been set". In generic code, this is often when you don't know whether null is a valid value -- perhaps you don't even check whether you're dealing with something that can be null.
Dec 15 2018
prev sibling parent Rubn <where is.this> writes:
On Thursday, 13 December 2018 at 13:47:34 UTC, Jonathan M Davis 
wrote:
 On Thursday, December 13, 2018 4:41:01 AM MST aliak via 
 Digitalmars-d wrote:
 On Tuesday, 11 December 2018 at 22:32:45 UTC, Jonathan M Davis

 wrote:
 Ultimately, allocation is the main difference here. Nullable 
 provides a way to emulate the behavior of a pointer with 
 regards to nullability without having to allocate on the 
 heap. An Optional or Maybe type is ultimately the same 
 thing, just with a different name. If it weren't for the 
 issue of heap allocation, it could easily argued that 
 pointers negate the need for any kind of 
 Nullable/Optional/Maybe type, because they provide that 
 functionality. And they don't even cause memory safety 
 issues if you're not doing pointer arithmetic. So, really, I 
 think that the need for heap allocation is _exactly_ the 
 issue here that these types are designed to solve and that 
 without that, a Nullable/Optional/Maybe type isn't adding 
 much.
This is not the point of optional types. They're just there to denote if a value exists or not.
Which you can do with a pointer. There is no need to have an "optional" type if you don't care about heap allocations. If you don't care about heap allocations, than you can just use T* and not bother with creating a library type. It even works if you already have a pointer, because you can have a pointer to a pointer: T**. Having an optional/maybe type allows you to indicate whether a value is present _without_ that extra heap allocation. It gives you pointer semantics with regards to whether a value is present without needing a pointer. So, ultimately, avoiding a heap allocation is the reason that such optional/maybe types exist, and it's why Nullable exists.
 At most, it's making it clear that it's expected that the 
 value can be null/empty/missing, because not all functions 
 that involve pointers consider null to be an acceptable 
 value.
That makes a big difference. With everything. Writing the code, maintaining code, reviewing code, coming back to code, getting in to a language. Principle of least surprises. Intuitiveness. These all matter: class C {} struct S {} * What is Nullable!(int*)? An int * that can be null? But wait... * What is Nullable!C? A reference type that can be null? Isn't that just "C" ? What is this adding? How does this make sense? * What does this do: "Nullable!C a = null; a.isNull;"? if it's false does that mean C exists? So C exists? * What is Nullable!S? A struct that can be null or a struct that may exist? Or was it just to avoid heap allocation? * What's the difference between null and any of those other types' isNull? * Nullable!C a = null; a.get; // should that throw/assert? These are all non-obvious.
The behavior is completely obvious if you understand that whether a Nullable is null refers to the Nullable itself and not the value that it contains. Looking at the documentation, it does need to be improved so that that is clearer. But if you understand that, then there is no confusion. And if you're not writing generic code, then the only reason to even put a pointer or reference in a Nullable in the first place is so that you can differentiate between a pointer or reference that has been given a value rather than being simply default-initialized. If you didn't care about that, then you would just use the pointer or reference directly, because the Nullable would do _nothing_ for you. It would be a pointless wrapper. And if you're writing generic code, then having isNull act differently for pointers than it does for value types would not work at all, because you'd have to special-case the code to handle the fact that assigning the Nullable a value could still result in isNull being true in the case where the Nullable contained a pointer or reference. So, the code would no longer be generic. And if it wasn't going to be generic, then you'd just use the naked pointer or reference rather than using a Nullable. The entire reason that Nullable was changed to allow types that are naturally nullable was so that generic code could use Nullable without having to be special-cased for pointers or references. That does come with the downside that isNull might be confusing if you don't understand what Nullable is, and the documentation clearly needs to be improved on that front, but there's no problem there with how Nullable works. It's just now a worse name, because it was changed to allow types that are naturally nullable and that can therefore have the value of null.
 IMHO, the only real naming issue that we have with Nullable 
 is that once it was changed to allow actual pointers 
 (whereas originally, it just contained types that could not 
 themselves be null), the property isNull became confusing to 
 some folks, because they thought that the null value of a 
 pointer and whether the Nullable itself was null were 
 related, when they're not (and if they were, it would cause 
 subtle bugs - especially in generic code).
Yes, and this is just bad. Bad bad bad. How can any API that causes subtle bugs be something anyone would want to keep around??
How on earth does Nullable cause subtle bugs? The only issue with it that I see is that the fact that it's aliased to its get member risks bugs when you change code that used a T to use Nullable!T. Without that alias, you'd be forced to update the code and make sure that it could handle when isNull was true, whereas right now, it can be trivial to just change a variable's type from T to Nullable!T and assume that it works, and even if you're careful and try to make the code use get and isNull properly, you can easily miss a place where the alias is used and end up with a bug. But if the alias is removed like the OP wants, then that entire problem goes away, and then I don't see why Nullable would cause subtle bugs. I'll grant you that now that Nullable allows types which are naturally nullable, the name is worse, because it does sometimes cause confusion around isNull. But it's against current policy to rename stuff in Phobos simply to give it a better name, and if the documentation is improved, that problem should largely go away. At that point, the only folks who would be confused are those that don't read the docs, and folks who don't read the documentation for the code they're reading or maintaining are going to be confused in general. If we were to start over, then yeah, it should probably be named Optional or Maybe or whatever rather than Nullable given that it accepts naturally nullable types, but I fail to see how its design causes bugs aside from the problems caused by the alias. And we should be able to fix that via deprecation, only breaking code that arguably should be broken in the process, whereas renaming stuff breaks _all_ code using Nullable, the vast majority of which is perfectly fine - which is why Walter and Andrei are against allowing renaming stuff. It breaks tons of code, and in general, the benefit is highly subjective if not outright negligible. It's probably worth more in this case than in many other cases, but it would still involve breaking tons of perfectly valid code. - Jonathan M Davis
https://github.com/dlang/phobos/commit/bfa6cd72ae83a977f272bf3520f3ef5da99eb7cf#diff-4e008aedb3026d4a84f58323e53bf017R1862 Had a good laugh when I found this. Looks like someone just didn't know what they were doing and someone was picking up their pieces.
Dec 14 2018
prev sibling next sibling parent reply Sebastiaan Koppe <mail skoppe.eu> writes:
On Tuesday, 11 December 2018 at 22:32:45 UTC, Jonathan M Davis 
wrote:
 the property isNull became confusing to some folks, because 
 they thought that the null value of a pointer and whether the 
 Nullable itself was null were related, when they're not (and if 
 they were, it would cause subtle bugs - especially in generic 
 code).
Could you elaborate on some of those subtle bug? And are they worse than the following? --- import std.typecons; class C { } void main() { Nullable!C c; assert(c.isNull); c = null; assert(!c.isNull); } --- What about having Nullable!T* decay into a wrapped pointer. Why does it need to keep its own bool when that information can be captured in the pointer? Or is there some difference between a Nullable!T* with a null value and one with no value? I thought no value and null to be the same thing. But if it is, why is it called Nullable?
Dec 13 2018
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, December 13, 2018 5:22:35 AM MST Sebastiaan Koppe via 
Digitalmars-d wrote:
 On Tuesday, 11 December 2018 at 22:32:45 UTC, Jonathan M Davis

 wrote:
 the property isNull became confusing to some folks, because
 they thought that the null value of a pointer and whether the
 Nullable itself was null were related, when they're not (and if
 they were, it would cause subtle bugs - especially in generic
 code).
Could you elaborate on some of those subtle bug? And are they worse than the following? --- import std.typecons; class C { } void main() { Nullable!C c; assert(c.isNull); c = null; assert(!c.isNull); } ---
I don't see anything wrong with that code. It's exactly how it's supposed to work. It's more confusing than would desirable, because Nullable was not originally intended to contain pointers, but having isNull be true would be a serious problem - especially for generic code.
 What about having Nullable!T* decay into a wrapped pointer. Why
 does it need to keep its own bool when that information can be
 captured in the pointer?
Take this code for example: Nullable!(int*) n = getValue(); assert(!n.isNull); Right now, you can rely on the fact that the Nullable contains a value after it's assigned a value and that therefore isNull is false, and calling get won't assert. However, if Nullable were to look at whether the internal pointer was null in order to determine whether the Nullable was "null," then suddenly, the fact that you assigned the Nullable a value would not guarantee that isNull was false, and calling get could therefore fail its assertion when it's called. That's bad enough when the code in question is specifically written with the idea that it's operating on a Nullable that contains a pointer. But it's absolutely horrible once you're dealing with generic code. Right now, you can write a templated function that operates on a Nullable without caring one whit about what type it contains, and it will behave the same regardless of the type. Meaning that something like this auto foo(T)(T t) { Nullable!T n = t; ... auto bar = t.get; ... } is guaranteed to work without asserting in get. However, if Nullable treated pointers differently from other types and didn't used a separate bool, then that code would have to worry about checking the type that the Nullable contains to see whether it was a pointer or class reference and in that case do extra checks rather than assuming that a Nullable that was assigned a value actually had a value and that calling get wouldn't assert. As such, treating types that can be given the value of null differently from types that can't be given a value of null is just begging for bugs. In addition, it defeats the entire purpose of Nullable allowing pointers. Originally, Nullable did not allow pointers or class references, because they could already be null. The main purpose of Nullable was to make it possible to have value types be nullable without allocating on the heap so that you could have a pointer. However, that meant that Nullable could not be used very well in generic code, because it could not contain any types that were naturally nullable. So, Nullable was changed to allow for such types, and it became possible to use Nullable in generic code without caring what type it contained. And as such, special-casing pointers in Nullable's implementation would completely defeat that, once again requiring that generic code using Nullable special-case pointers in order to avoid bugs. Now, a side benefit of allowing Nullable to contain pointers and class references while using a separate bool to denote whether the Nullable contains a value or not is that you can have a Nullable treat null as a valid value and differentiate between having a pointer whose value is null and one which hasn't been given a value yet - e.g. if you have a case where you want to know whether a variable has been initialized yet, and it's allowed to be given the value of null, the fact that pointers are null by default makes it so that you can't tell whether the pointer has been given a value just by looking at the pointer. In that case, you basically need a separate bool that keeps track of whether the pointer has been initialized yet. And while that wasn't really the point of allowing Nullable to contain pointers, some folks have been using it for that purpose. And it does fit in nicely with one of the main use cases of using Nullable with value types. Nullable is often used simply to denote whether the variable has been given a value yet, because the default value is considered valid in a particular piece of code and therefore can't be used to distinguish between a default-initialized variable and one that's been given a real value. Having Nullable treat pointers differently would destroy that, and it would break existing code. Honestly, there's no point in having Nullable use the null value of pointers to indicate whether isNull is true, because if that's what you wanted, you could just use the pointer directly without using Nullable at all. And not being able to rely on isNull being false after you assign a value to a Nullable would seriously hamper Nullable in generic code, because it couldn't actually be used generically. So, while the name Nullable has become somewhat confusing now that Nullable can contain pointers, from the point of view of how Nullable is used, it makes no sense for it to treat pointers differently. If it were going to do that, it might as well have never been changed to allow pointers.
 Or is there some difference between a Nullable!T* with a null
 value and one with no value? I thought no value and null to be
 the same thing. But if it is, why is it called Nullable?
It's called Nullable, because it was originally intended just for types which can't naturally be null. However, as I explained above, that doesn't play well with generic code, and it's sometimes useful to differentiate between a variable that's been default-initialized and one that's been given an actual value, and in the case of a pointer, that actual value could be null. The fact that Nullable now allows pointers and class references does unfortunately make it more confusing than it was originally. In light of the changes that have been made to it, it probably should be called something else, and isNull should probably be isEmpty instead, but it's against current policy to rename things in Phobos. We replace things when their design turns out to be bad, but we don't simply rename them anymore. Walter and Andrei do not consider renaming things to be worth the code breakage. And IMHO, if the documentation is sufficiently clear, this is really a non-issue. isNull indicates whether the Nullable is null, not whether the value it contains is null. The fact that there is any confusion here does indicate that the documentation needs to be improved, but as long as you understand that isNull refers to the state of the Nullable itself and not the value that it contains, it works perfectly fine as-is. - Jonathan M Davis
Dec 13 2018
parent reply Sebastiaan Koppe <mail skoppe.eu> writes:
On Thursday, 13 December 2018 at 13:14:13 UTC, Jonathan M Davis 
wrote:
 import std.typecons;
 class C {
 }
 void main() {
      Nullable!C c;
      assert(c.isNull);
      c = null;
      assert(!c.isNull);
 }
 ---
I don't see anything wrong with that code. It's exactly how it's supposed to work.
It is confusing, it doesn't follow the principle of least astonishment.
 Right now, you can write a templated function that operates on 
 a Nullable without caring one whit about what type it contains, 
 and it will behave the same regardless of the type. Meaning 
 that something like this

 auto foo(T)(T t)
 {
     Nullable!T n = t;
     ...
     auto bar = t.get;
     ...
 }
In languages where nullable/optional/maybe is more central, you are advised to avoid calling get directly, and you are expected to always map or foreach over it. Essentially it is a range with 0 or 1 element.
 Nullable is often used simply to denote whether the variable
 has been given a value yet
Do you consider null a value? Because Nullable does. --- auto p = Nullable!(int*)(someFunctionThatReturnsNull()); while (p.isNull) { // never happens } --- --- int* p = someFunctionThatReturnsNull(); while (p is null) { // does } ---
Dec 14 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Friday, December 14, 2018 4:39:16 PM MST Sebastiaan Koppe via 
Digitalmars-d wrote:
 On Thursday, 13 December 2018 at 13:14:13 UTC, Jonathan M Davis

 wrote:
 import std.typecons;
 class C {
 }
 void main() {

      Nullable!C c;
      assert(c.isNull);
      c = null;
      assert(!c.isNull);

 }
 ---
I don't see anything wrong with that code. It's exactly how it's supposed to work.
It is confusing, it doesn't follow the principle of least astonishment.
Honestly, I have never found it counfusing, but I don't disagree that having a different name would be less confusing for some folks. Regardless of the naming though, Nullable has exactly the semantics that it needs to have to make any sense to use it with naturally nullable types. If isNull cared about the value of what the Nullable contained, pointers would not work the same as other types inside a Nullable, making Nullable treacherous to use in generic code, and if the code is not generic, and isNull just tells you whether the pointer is null, then you might as well use the pointer directly. Using Nullable!(T*) can make sense in non-generic when you want to indicate whether the variable has been assigned a value rather than being default-initialized, and it makes sense in generic code where you don't care what type you're operating on. But Nullable!(T*) makes no sense whatsoever if isNull indicates anything about the pointer's value.
 In languages where nullable/optional/maybe is more central, you
 are advised to avoid calling get directly, and you are expected
 to always map or foreach over it.

 Essentially it is a range with 0 or 1 element.
I have never seen an Optional type that worked that way or worked with code that did anything like that. That obviously does not mean that such code doesn't exist, but personally, it strikes me as a bizarre way to operate, and I've never written code that way. Having a type that contains zero or more elements (such as a range) makes sense to me, but mapping that API onto a type that can contain one or zero just seems bizarre to me. That being said, it's pretty trivial for someone to write a function that operates on a pointer or Nullable that way if that's what they want. Either way, it would totally screw with code that does consider null to be distinct from not having a value for Nullable to treat isNull as having anything to do with the value of that's contained in the Nullable. e.g. IIRC, when someone previously suggested making such a change to Nullable, it was pointed out that it would break vibe.d, because it has code that uses Nullable!(T*) with null having a distinct meaning separate from the Nullable having no value.
 Nullable is often used simply to denote whether the variable
 has been given a value yet
Do you consider null a value? Because Nullable does.
Whether null is a valid value depends entirely on how the code works. Putting a pointer inside a Nullable is essentially the same as having a pointer to a pointer. T** is nullable, but whether the outer pointer is null says nothing about whether the T* it points to is null. Some code would consider having the T* pointed to by the T** as null to be a perfectly valid value, whereas other code would not. And if you want to indicate whether a variable has been given a value or not when the init value for that type is a valid value, then you pretty much have to use either a pointer to that value or something like Nullable which uses a separate bool to indicate whether variable has been given a value. - Jonathan M Davis
Dec 14 2018
prev sibling parent Atila Neves <atila.neves gmail.com> writes:
On Tuesday, 11 December 2018 at 22:32:45 UTC, Jonathan M Davis 
wrote:
 On Tuesday, December 11, 2018 2:48:36 PM MST aliak via 
 Digitalmars-d wrote:
 [...]
Ultimately, allocation is the main difference here. Nullable provides a way to emulate the behavior of a pointer with regards to nullability without having to allocate on the heap. An Optional or Maybe type is ultimately the same thing, just with a different name. If it weren't for the issue of heap allocation, it could easily argued that pointers negate the need for any kind of Nullable/Optional/Maybe type, because they provide that functionality. And they don't even cause memory safety issues if you're not doing pointer arithmetic.
There's another fundamental difference with regards to pointers: there's no equivalent to `null`, which is this weird special value that is compatible with all pointer types.
Dec 13 2018
prev sibling parent Jacob Carlborg <doob me.com> writes:
On 2018-12-10 16:47, aliak wrote:

 Sounds like a step forward, deprecating that.

 Do people consider Nullable a type of Optional thing in general?
No, it's not the same as Optional.
 I have  a written a bit more details here [0] but basically I don't think
 deprecating .get is enough to make it "safe" - granted - depending on
 what safe means. In the context of D a segfault is safe so maybe that's
 ok. But then deprecating get doesn't add safety in that context anyway.
"get" has a few use cases, i.e. when you need to explicitly check if the value is present or not. Then, if it is present, you can use "get" to minimize overhead. But it's rare to need it. -- /Jacob Carlborg
Dec 10 2018
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 12/10/18 5:01 AM, FeepingCreature wrote:
 Having recently been reminded that `alias Nullable.get this` exists, I'm 
 considering a pull request to deprecate it. What's the sentiment on 
 `alias Nullable.get this` in the community? My unchanged stance is that 
 it's a blight and an interminable source of impossible to find runtime 
 bugs, and an anti-feature that misses the point of Nullable to provide 
 safe optional types.
 
 The typical problem goes like this:
 
 1. A dependency switches from T to Nullable!T.
 2. You update your dependencies.
 3. Your program still compiles (because Nullable!T silently casts to "T 
 or exception") and you notice nothing.
 4. Sometime later, your program crashes in production.
Wow, I didn't realize that get, in production with asserts off, is no different than just any T. It's practically useless. I don't think we can simply deprecate that feature. All you will see is people adding "get" to all the accesses, but still resulting in the same problem. I was going to suggest changing the assert to an enforce, but that changes the nothrow semantics. I think we'd be better off adding a new type, which does what people want, and deprecating Nullable completely. -Steve
Dec 10 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Monday, December 10, 2018 9:02:32 AM MST Steven Schveighoffer via 
Digitalmars-d wrote:
 On 12/10/18 5:01 AM, FeepingCreature wrote:
 Having recently been reminded that `alias Nullable.get this` exists, I'm
 considering a pull request to deprecate it. What's the sentiment on
 `alias Nullable.get this` in the community? My unchanged stance is that
 it's a blight and an interminable source of impossible to find runtime
 bugs, and an anti-feature that misses the point of Nullable to provide
 safe optional types.

 The typical problem goes like this:

 1. A dependency switches from T to Nullable!T.
 2. You update your dependencies.
 3. Your program still compiles (because Nullable!T silently casts to "T
 or exception") and you notice nothing.
 4. Sometime later, your program crashes in production.
Wow, I didn't realize that get, in production with asserts off, is no different than just any T. It's practically useless. I don't think we can simply deprecate that feature. All you will see is people adding "get" to all the accesses, but still resulting in the same problem. I was going to suggest changing the assert to an enforce, but that changes the nothrow semantics. I think we'd be better off adding a new type, which does what people want, and deprecating Nullable completely.
Given that it's a bug in the program to access get (be it explicitly or implicitly) on a Nullable that's null, I don't think that it's at all appropriate to use an Exception. An assertion makes perfect sense. The only potential issue I see there is if someone thinks that such a bug is critical enough that it needs to always be checked for even with -release, in which case, enforce could be used with an Error. I'm not convinced that it's a severe enough issue to merit adding a check that's always in place, but it could be done with Nullable as it stands without breaking code. - Jonathan M Davis
Dec 10 2018
prev sibling next sibling parent John Chapman <johnch_atms hotmail.com> writes:
On Monday, 10 December 2018 at 10:01:45 UTC, FeepingCreature 
wrote:
 Having recently been reminded that `alias Nullable.get this` 
 exists, I'm considering a pull request to deprecate it. What's 
 the sentiment on `alias Nullable.get this` in the community? My 
 unchanged stance is that it's a blight and an interminable 
 source of impossible to find runtime bugs, and an anti-feature 
 that misses the point of Nullable to provide safe optional 
 types.

 The typical problem goes like this:

 1. A dependency switches from T to Nullable!T.
 2. You update your dependencies.
 3. Your program still compiles (because Nullable!T silently 
 casts to "T or exception") and you notice nothing.
 4. Sometime later, your program crashes in production.

 Thoughts?
I would deprecate "alias get this", rename the version of "get" that takes a fallback to "orDefault" and add "alias orDefault this". Going further, I'd also deprecate the weird "nullify" and "isNull", and instead add overloads of opAssign and opEquals for null.
Dec 10 2018
prev sibling next sibling parent Neia Neutuladh <neia ikeran.org> writes:
On Mon, 10 Dec 2018 10:01:45 +0000, FeepingCreature wrote:
 Having recently been reminded that `alias Nullable.get this` exists, I'm
 considering a pull request to deprecate it. What's the sentiment on
 `alias Nullable.get this` in the community?
I already thought that Nullable was clunky (!nullable.isNull: is it false that the thing has no value? -- I just want to know if it has a value). With that alias, I'm kind of horrified. Why does this even exist? Why is it Nullable instead of Optional or Maybe? The thing about null is that it segfaults when you use it, which is not the key thing about an optional value. Why does it have this automatic bug pit?
Dec 10 2018
prev sibling next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Monday, December 10, 2018 3:01:45 AM MST FeepingCreature via Digitalmars-
d wrote:
 Having recently been reminded that `alias Nullable.get this`
 exists, I'm considering a pull request to deprecate it. What's
 the sentiment on `alias Nullable.get this` in the community? My
 unchanged stance is that it's a blight and an interminable source
 of impossible to find runtime bugs, and an anti-feature that
 misses the point of Nullable to provide safe optional types.

 The typical problem goes like this:

 1. A dependency switches from T to Nullable!T.
 2. You update your dependencies.
 3. Your program still compiles (because Nullable!T silently casts
 to "T or exception") and you notice nothing.
 4. Sometime later, your program crashes in production.

 Thoughts?
Personally, I don't think that it's all that big a deal, but I'm not really against deprecating the alias either. Given that we don't have implicit construction in D, the alias already fails at its goal of making a Nullable!T work like a T (though even _with_ implicit construction, swapping out a T for a Nullable!T would break code involving template constraints, so implicit construction isn't the only problem there, but it's the most obvious one). So, in general, I'm inclined to argue that it's just better practice to use get explicitly, and I wouldn't mind it being deprecated. However, IIRC, someone already tried to deprecate the alias and ran into a compiler bug that prevented it - though unfortunately, I can't find the bug report at the moment. I recall it being an issue with getting deprecation messages from the alias even when it wasn't used explicitly, but when I try a simple test case, I get a different bug entirely. This code snippet struct S { int get() { return 42; } deprecated alias get this; } void main() { S s; int i = s; } doesn't result in any deprecation messages at all. Based on that, without some compiler fixes, it's not actually possible to deprecate the alias. - Jonathan M Davis
Dec 10 2018
parent FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 10 December 2018 at 17:43:40 UTC, Jonathan M Davis 
wrote:
 However, IIRC, someone already tried to deprecate the alias and 
 ran into a compiler bug that prevented it - though 
 unfortunately, I can't find the bug report at the moment. I 
 recall it being an issue with getting deprecation messages from 
 the alias even when it wasn't used explicitly, but when I try a 
 simple test case, I get a different bug entirely. This code 
 snippet

 struct S
 {
     int get()
     {
         return 42;
     }

     deprecated alias get this;
 }

 void main()
 {
     S s;
     int i = s;
 }

 doesn't result in any deprecation messages at all. Based on 
 that, without some compiler fixes, it's not actually possible 
 to deprecate the alias.

 - Jonathan M Davis
Deprecating get() works, for what it's worth. So the way to go would be changing Nullable to alias deprecated_get_ this;
Dec 10 2018
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Dec 10, 2018 at 10:43:40AM -0700, Jonathan M Davis via Digitalmars-d
wrote:
[...]
 However, IIRC, someone already tried to deprecate the alias and ran
 into a compiler bug that prevented it - though unfortunately, I can't
 find the bug report at the moment. I recall it being an issue with
 getting deprecation messages from the alias even when it wasn't used
 explicitly, but when I try a simple test case, I get a different bug
 entirely. This code snippet
 
 struct S
 {
     int get()
     {
         return 42;
     }
 
     deprecated alias get this;
 }
 
 void main()
 {
     S s;
     int i = s;
 }
 
 doesn't result in any deprecation messages at all. Based on that,
 without some compiler fixes, it's not actually possible to deprecate
 the alias.
[...] Hmm. I wonder if a circumlocution similar to the following might work? struct S { int get() { return 42; } deprecated int deprecatedGet() { return get(); } alias deprecatedGet this; } The resulting error message leaves a lot to be desired, though. T -- "Outlook not so good." That magic 8-ball knows everything! I'll ask about Exchange Server next. -- (Stolen from the net)
Dec 10 2018