www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Nullable!T with T of class type

reply kdevel <kdevel vogtner.de> writes:
Just stumbled over the following design:

    class S {...}

    class R {
       :
       Nullable!S s;
       :
    }

s was checked in code like

    R r;
    :
    if (r.s is null)
       throw new Exception ("some error message");

At runtime the following was caught:

     fatal error: caught Throwable: Called `get' on null Nullable!S

Why can't this programming error be detected at compile time? Is 
it possible
to "lower" the Nullable operations if T is a class type such that 
there
is only one level of nullification?

BTW: https://dlang.org/library/std/typecons/nullable.html 
contains duplicate sections:

     Function nullable
     Function nullable
     Struct Nullable
     Struct Nullable
Jun 25 2018
next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Monday, June 25, 2018 19:40:30 kdevel via Digitalmars-d-learn wrote:
 Just stumbled over the following design:

     class S {...}

     class R {

        Nullable!S s;

     }

 s was checked in code like

     R r;

     if (r.s is null)
        throw new Exception ("some error message");

 At runtime the following was caught:

      fatal error: caught Throwable: Called `get' on null Nullable!S

 Why can't this programming error be detected at compile time?
If you have a function that accepts Nullable!T, when that function is called, how on earth is it going to know whether the argument it received was null at compile time? That depends entirely on the argument, which could have come from anywhere. So, in the general case, the compiler can't possibly determine whether a variable is null or not at compile time. And as far as just within a function goes, Nullable is a library type. There's nothing special about it. The compiler has no magic knowledge about what it does or how it functions. So, how would it know that R r; r.foo(); was bad code? And honestly, this sort of problem actually gets surprisingly thorny even if you tried to bake this into the compiler for a built-in type. Sure, it would be straightforward for the compiler to see that int* i; *i = 42; is dereferencing null, but as soon as you start adding if statements, function calls, etc. it quickly becomes impossible for the compiler to accurately determine whether the pointer is null or not. Any such attempt will inevitably end up with false positives, forcing you to do stuff like assign variables values when you know that it's unnecessary - it would complicate the compiler considerably to even make the attempt. It's far simpler to just treat it as a runtime error. Even more restricted languages such as Java do that. Java does try to force you to initialize stuff (resulting in annoying false positives at times), but in general, it still can't guarantee when a variable is null or not and is forced to insert runtime null checks.
 Is it possible
 to "lower" the Nullable operations if T is a class type such that
 there
 is only one level of nullification?
It's been discussed before, but doing so would make the behavior of Nullable depend subtly on what type it contained and risks bugs in generic code. Also, there _is_ code out there which depends on the fact that you can have a Nullable!MyClass where the variable has a value and that value is a null reference. If you really don't want the extra bool, then just don't use Nullable. Class references are already naturally nullable. - Jonathan M Davis
Jun 25 2018
next sibling parent Nathan S. <no.public.email example.com> writes:
On Monday, 25 June 2018 at 22:58:41 UTC, Jonathan M Davis wrote:
 Java does try to force you to initialize stuff (resulting in 
 annoying false positives at times), but in general, it still 
 can't guarantee when a variable is null or not and is forced to 
 insert runtime null checks.
Java can be somewhat clever about this though. Often it just needs to perform a single null check in a method body and thereafter knows the pointer can't be null and elides the check.
Jun 26 2018
prev sibling parent reply kdevel <kdevel vogtner.de> writes:
On Monday, 25 June 2018 at 22:58:41 UTC, Jonathan M Davis wrote:
 On Monday, June 25, 2018 19:40:30 kdevel via 
 Digitalmars-d-learn wrote:
     R r;

     if (r.s is null)
        throw new Exception ("some error message");
[...]
 Why can't this programming error be detected at compile time?
If you have a function that accepts Nullable!T, when that function is called, how on earth is it going to know whether the argument it received was null at compile time?
That was not the error. The error was that I used is null instead of .isNull() which lead to a different Exception (Error). [...]
 If you really don't want the extra bool, then just don't use 
 Nullable. Class references are already naturally nullable.
Sure.
Jun 26 2018
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Tuesday, June 26, 2018 19:03:20 kdevel via Digitalmars-d-learn wrote:
 On Monday, 25 June 2018 at 22:58:41 UTC, Jonathan M Davis wrote:
 On Monday, June 25, 2018 19:40:30 kdevel via

 Digitalmars-d-learn wrote:
     R r;

     if (r.s is null)

        throw new Exception ("some error message");
[...]
 Why can't this programming error be detected at compile time?
If you have a function that accepts Nullable!T, when that function is called, how on earth is it going to know whether the argument it received was null at compile time?
That was not the error. The error was that I used is null instead of .isNull() which lead to a different Exception (Error).
Well, get checks whether the Nullable isNull, and throws an Error if it is, which is what you showed in your original post. If you want a Nullable to have the value of null, then you have to explicitly set it to null. Nullable really doesn't have anything to do with the null value for pointers or references beyond the fact that it allows a way to emulate that behavior for non-nullable types by using a bool. isNull returns whether the Nullable has been given a value or not, whereas checking the value with is null means checking whether it has a value and whether that value is null. If that behavior is not what you want, then you will have to find a different solution, though honestly, I don't understand why folks keep trying to put nullable types in Nullable in non-generic code. - Jonathan M Davis
Jun 26 2018
parent reply kdevel <kdevel vogtner.de> writes:
On Tuesday, 26 June 2018 at 21:54:49 UTC, Jonathan M Davis wrote:
 [H]onestly, I don't understand why folks keep trying to put 
 nullable types in Nullable in non-generic code.
How do you signify that a struct member of class type is optional?
Jun 28 2018
next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, June 28, 2018 18:10:07 kdevel via Digitalmars-d-learn wrote:
 On Tuesday, 26 June 2018 at 21:54:49 UTC, Jonathan M Davis wrote:
 [H]onestly, I don't understand why folks keep trying to put
 nullable types in Nullable in non-generic code.
How do you signify that a struct member of class type is optional?
Structs aren't nullable, so wrapping them in a Nullable makes perfect sense. Whether they happen to be on the stack or members of another type is irrelevant to that. It's wrapping types like pointers and class references in a Nullable that's an odd thing to do - the types where someone might ask why the extra bool is necessary in the Nullable. Wrapping them in a Nullable makes sense in generic code, because the code isn't written specifically for them, but something like Nullable!MyClass in non-generic code is pointless IMHO, because a class reference is already nullable. - Jonathan M Davis
Jun 28 2018
next sibling parent reply kdevel <kdevel vogtner.de> writes:
On Thursday, 28 June 2018 at 19:22:38 UTC, Jonathan M Davis wrote:
 Nullable makes sense in generic code, because the code isn't 
 written specifically for them, but something like 
 Nullable!MyClass in non-generic code is pointless IMHO, because 
 a class reference is already nullable.
It is already technically nullable. But how do you signify to the reader of the code that a class member in a struct or in a class may intentionally (not purely technically) be null? If I had written class R { : S s; : } with S being a class. The reader of the code cannot spot if s is optional. In my code I could not have declared S as a struct since it implements an interface.
Jun 28 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, June 28, 2018 19:45:52 kdevel via Digitalmars-d-learn wrote:
 On Thursday, 28 June 2018 at 19:22:38 UTC, Jonathan M Davis wrote:
 Nullable makes sense in generic code, because the code isn't
 written specifically for them, but something like
 Nullable!MyClass in non-generic code is pointless IMHO, because
 a class reference is already nullable.
It is already technically nullable. But how do you signify to the reader of the code that a class member in a struct or in a class may intentionally (not purely technically) be null? If I had written class R { S s; } with S being a class. The reader of the code cannot spot if s is optional. In my code I could not have declared S as a struct since it implements an interface.
That's something that you have to worry about with all code involving classes. Putting Nullable on a class reference does seem like it would make it clear that you want it to be nullable, but it doesn't really, and the lack of nullable doesn't mean that it can't purposefully be null. If Nullable couldn't contain a null, or it didn't have a separate bool for null, then maybe it would help clarify, but that would result in subtle bugs in generic code whenever null is involved, and there are cases where code purposefully uses Nullable to indicate that the variable has not been initialized yet but where null is considered a reasonable value for the type. So, the fact that Nullable is there doesn't really clarify matters. It just opens up the question of what happens with null. In my experience, it's usually pretty clear from the API or design of a piece of code whether it's intended to accept null or not (usually, the answer is no) and that folks document what happens if null is provided if it's supposed to be accepted, but regardless, any case where it's not clear needs to be documented so that it is clear. Certainly, relying on Nullable for that is going to be error-prone, because Nullable allows null and can take advantage of the fact that isNull has nothing to do with the value of null. So, if the intention is that a Nullable!MyClass never have a value of null, then that needs to be documented, essentially defeating the purpose of putting it in a Nullable in the first place. At that point, it would just be more user-friendly to let it be null and document it as such than to use Nullable and require that someone provide an uninitialized or cleared Nullable instead of just using null. Now, some folks do use Nullable to try and handle whether a class reference is null, but given that it doesn't really solve the problem of what happens with null and still requires documenting the intent, I think that it's more misleading than enlightening to use Nullable on class references. If you're going to wrap class references to try and indicate whether they can be null or not, it makes far more sense to wrap a class reference in a wrapper intended to indicate that it _can't_ be null than one that's intended to indicate that it can be. - Jonathan M Davis
Jun 28 2018
prev sibling parent Jordan Wilson <wilsonjord gmail.com> writes:
On Thursday, 28 June 2018 at 19:22:38 UTC, Jonathan M Davis wrote:
 On Thursday, June 28, 2018 18:10:07 kdevel via 
 Digitalmars-d-learn wrote:
 On Tuesday, 26 June 2018 at 21:54:49 UTC, Jonathan M Davis 
 wrote:
 [H]onestly, I don't understand why folks keep trying to put 
 nullable types in Nullable in non-generic code.
How do you signify that a struct member of class type is optional?
Structs aren't nullable, so wrapping them in a Nullable makes perfect sense. Whether they happen to be on the stack or members of another type is irrelevant to that. It's wrapping types like pointers and class references in a Nullable that's an odd thing to do - the types where someone might ask why the extra bool is necessary in the Nullable. Wrapping them in a Nullable makes sense in generic code, because the code isn't written specifically for them, but something like Nullable!MyClass in non-generic code is pointless IMHO, because a class reference is already nullable. - Jonathan M Davis
Reading inpput from a csv file, and the value could either be blank, na, or numeric. Nullable!MyClass could be used to represent 3 states in your programming logic. I'm not saying this is the best way to represent ternary states, but it's not unreasonable. Jordan Wilson
Jun 28 2018
prev sibling parent aliak <something something.com> writes:
On Thursday, 28 June 2018 at 18:10:07 UTC, kdevel wrote:
 On Tuesday, 26 June 2018 at 21:54:49 UTC, Jonathan M Davis 
 wrote:
 [H]onestly, I don't understand why folks keep trying to put 
 nullable types in Nullable in non-generic code.
How do you signify that a struct member of class type is optional?
So there're no optional type in D (ala Swift, Kotlin, Scala, etc). There are a number of workarounds you can use to achieve the same type of behavior. You can use ranges to denote "some" value or no value (empty range). But it's a bit inconvenient and the APIs to get that running say nothing about intent. You can create a lightweight Maybe type: https://stackoverflow.com/questions/27241908/maybe-types-in-d I've implemented an optional type as well that includes safe dispatching, but it's not nogc right now -> https://github.com/aliak00/optional I think there's another optional type on dub somewhere as well. But using Nullable!T just defers the problem that optional solves: if (nullable.isNull) { nullable.get.doSomething(); } vs: if (ptr !is null) { ptr.doSomething(); } meh... It's more of a tool to allow you to give any type nullability semantics. Cheers, - Ali
Jun 28 2018
prev sibling parent reply Nathan S. <no.public.email example.com> writes:
On Monday, 25 June 2018 at 19:40:30 UTC, kdevel wrote:
 Is it possible
 to "lower" the Nullable operations if T is a class type such 
 that there
 is only one level of nullification?
Yes: https://run.dlang.io/is/hPxbyf ---- template Nullable(S) { import std.traits : isPointer, isDynamicArray; static if (is(S == class) || is(S == interface) || is(S == function) || is(S == delegate) || isPointer!S || isDynamicArray!S) { alias Nullable = S; } else { static import std.typecons; alias Nullable = std.typecons.Nullable!S; } } ----
Jun 26 2018
parent kdevel <kdevel vogtner.de> writes:
On Tuesday, 26 June 2018 at 14:32:59 UTC, Nathan S. wrote:
 On Monday, 25 June 2018 at 19:40:30 UTC, kdevel wrote:
 Is it possible
 to "lower" the Nullable operations if T is a class type such 
 that there
 is only one level of nullification?
Yes: https://run.dlang.io/is/hPxbyf ---- template Nullable(S)
[...]
 ----
Works nicely. Thanks!
Jun 26 2018