Earnestly thinking NULL is a mistake is a symptom


  • Banned

    @CoyneTheDup said:

    But the people who proposed this still ignored the fact that sooner or later you have to know the difference. Suppose you have Person p, which is an Optional<T> and it has similar name and address members. You want to create a mailing label:

    Label lab = new Label;
    lab.setName(p.name());
    lab setAddress(p.name());


    Optional<Person> doesn't have method name(). To access it, you must first access the inner value and call it on this inner value, and to access the inner value (assuming sane interface, not that Java 8 thing), you need to explicitly perform check.

    And if you go and correct yourself now that you meant that p is Person, not Option<Person>, and it's name() that returns the optional value, my argument works the same way.

    @CoyneTheDup said:

    That way you can copy bogus data without having to worry about the copy failing. Does this improve the output?

    Value/reference semantics discussion is orthogonal to nullable/non-nullable variables discussion. Please don't mix the two.

    @CoyneTheDup said:

    Unicode was supposed to be simple, then it met the world, now it's just as messy as the world.

    Though to be honest, a fair share of Unicode problems were created by Unicode itself, in response to problems that don't exist in real world. For example, lack of definite canonical form of any most combined characters, or direction override shitfest.

    @CoyneTheDup said:

    What if you have to declare everything Optional because it can be?

    Then you fucking declare everything optional for fuck's sake.


  • I survived the hour long Uno hand

    @anonymous234 said:

    What writing system is there that's not in Unicode?

    What's the codepoint for



  • You convinced me, from now on I'll ditch strings and store names as a SVG documents to allow for every possible visual representation, as well as animation and interactivity.

    And I'll change my name to a full implementation of Tetris.



  • What makes you think all names can be represented graphically? Some names are sound only; no written form exists.



  • Don't worry, SVG 1.2 will support embedded audio and video objects.

    (Though maybe I should have gone with HTML instead, I won't debate that)

    (OK, but seriously... how would you store a name with no graphical representation? I'd just put a null and let the humans handle it)



  • Maybe you could use an instance of Discourse as your name?



  • @Gaska said:

    Optional<Person> doesn't have method name(). To access it, you must first access the inner value and call it on this inner value, and to access the inner value (assuming sane interface, not that Java 8 thing), you need to explicitly perform check.

    Yeah, I didn't understand that when I wrote that message. I do now. Still don't think it saves me much, though, because I either have to check or default. In the domain example I provided, that would be check, because default isn't going to cut it.

    At the same time, I think you missed the point that p is a person object, which contains optional members. So, yes, p would have a "name" method (or, canonically, getName()). So I would have:

       if ( p.getName().isPresent() && p.getName().isPresent())
       {
          label.setName(p.getName().get());
          label.setAddress(p.getAddress().get());
       }
    

    Some might counter, no, you need to use the simpler....

       label.setName(p.getName().orElse("No name"));
       label.setAddress(p.getAddress().orElse("No address"));
    

    Or even...

       p.getName().ifPresent(x -> label.setName(x));
       p.getAddress().ifPresent(x -> label.setAddress(x));
    

    ...but I will counter that either of these merely produces a broken label on a real-world letter.

    I suppose it's a matter of taste, but as I understand it, the original objections were (1) you have to read the JavaDoc for Person and if you don't, broken code; and (2) you always have to write all these checks for null all the time.

    I am starting to get a glimmer of how Optional improves the semantics...you can't ignore the fact you're dealing with an optional. But I'm not seeing how it improves the resulting code. Everywhere you use these optional items, you'll still be doing checks, even if orElse() or ifPresent().

    Further, I predict that lazy people using these will get in the habit of using orElse() or ifPresent() all the time, without bothering to think through the implications. That might save Exceptions being thrown, but Exceptions are not our only concern with respect to the correctness of programs.

    That is: A program is not correct, simply because no Exception is thrown.



  • The thing to consider is: what would those same lazy people do if dealing with nullable references?

    In a language where class types have value semantics, an optional wrapper type makes more sense. In a language like Java, I'm actually not too sure there is much benefit at all. I'd like to say there is though, or else they wouldn't have added it to the language.



  • @CoyneTheDup said:

    But I'm not seeing how it improves the resulting code. Everywhere you use these optional items, you'll still be doing checks, even if orElse() or ifPresent().

    Which is why I see no benefit of optional over, say, raw pointers. The "no silver bullet" paper explains this better than I can, but the simple fact is that if you have something that may or may not be there, there is no abstraction you can create that would let you treat that thing as if it was and have the program behave correctly.

    You have two states. Thus, your code has to deal with those two states.



  • @LB_ said:

    I'd like to say there is though, or else they wouldn't have added it to the language.

    I'd like to think that, too, but I'm just not that big an idiot. Things are added to languages all the time for political reasons. We like to pretend we computer scientists are not political, but unfortunately...well, think language wars if you want an example.

    Note that I'm not saying Optional is worthless; I'm just pointing out that just because a feature exists doesn't mean it was ever a good idea. In some cases it might exist because the enthusiasts shouted down the objectors.


  • Banned

    @CoyneTheDup said:

    Still don't think it saves me much, though, because I either have to check or default.

    The difference is, if you forget to do it, the code won't compile. And it makes the world of difference. People are forgetful.


    @CoyneTheDup said:

    At the same time, I think you missed the point that p is a person object, which contains optional members.

    Um...
    @Gaska said:
    And if you go and correct yourself now that you meant that p is Person, not Option<Person>, and it's name() that returns the optional value, my argument works the same way.


    @CoyneTheDup said:

    ```
    label.setName(p.getName().get());

    If you do it like this, you explicitly agreed to crash if name is empty. It might be not immediately obvious, because "get" is such a generic and overused word in programming that you just skip your eye over it, but I blame bad interface.
    
    ---
    
    @CoyneTheDup <a href="/t/via-quote/51020/57">said</a>:<blockquote>but I will counter that either of these merely produces a broken label on a real-world letter.</blockquote>
    You can't blame the language if you have fucked up the business logic with your own hands. If the default you provided is inappropriate in this context, you shouldn't have provided this default in the first place. Display error message or something.
    
    ---
    
    @LB_ <a href="/t/via-quote/51020/58">said</a>:<blockquote>In a language where class types have value semantics, an optional wrapper type makes more sense.</blockquote>
    It makes as much sense in all languages regardless of whether it has value or reference semantics. The problem starts when references become nullable. But this isn't intrinsic property of reference semantics - just shitty implementation in a given language.


  • @Gaska said:

    It makes as much sense in all languages regardless of whether it has value or reference semantics.

    Why?


  • Banned

    Why shouldn't it?



  • With std::optional<T>, you can safely copy it without worrying about if it contains a value or not, so long as T is copyable. With Optional<T> does it even make sense to talk about copying? The language you use drastically affects the way you perceive usefulness.


  • Banned

    In a language with nullable references, yes, this makes little sense. So let's forget about them for a minute and focus only on languages with reference semantics where references can never be null.

    First and foremost - the optional discussion is orthogonal to the copying discussion, and the two problems never overlap until you copy an optional.

    We have some value of type Optional<T>. To access it like T, we must explicitly check if it's null beforehand. When the check passes, then we have valid reference pointing to valid object, and all its members point to valid objects too (unless they're optional too - then we have to do another check). If something is not nullable and you have reference to it, there's no way in hell it won't be a valid object. You can do what the fuck you want with it and don't have to worry about nulls because there are no nulls. And if there are, you'll get wordy error from the compiler as a metaphor of punching you in the face.

    When you copy Optional<T>, it works exactly the same way as if you were copying T, except you might be copying nothing. It works like this regardless of if you have values or references. And if you extract the inner T object from Optional<T> in reference-semantics language, the obtained reference will be always valid even after the original optional object becomes empty - because you have reference to its inner value, not the container, so changing the container doesn't affect the object pointed by your reference.



  • @Gaska said:

    First and foremost - the optional discussion is orthogonal to the copying discussion, and the two problems never overlap until you copy an optional.

    But that's exactly what I am trying to say - some languages have less overlap than others. I agree with everything else you're saying.



  • @Gaska said:

    If you do it like this, you explicitly agreed to crash if name is empty. It might be not immediately obvious, because "get" is such a generic and overused word in programming that you just skip your eye over it, but I blame bad interface.

    But, but, but...the programmer tested it with one Person, and it worked. Remember, we're talking about people here too lazy to read the doc and do if (pwithoutoptional.getName() != null). The point is that saying, "Everything is better with Optional," doesn't deal with these people. And these people will yield broken programs...there just won't be Exceptions.

    This is not the first time I've seen this argument that, "All my programs would be perfect if it just weren't for those @#%@^! Exceptions." Which is really begging the issue, because the Exceptions are nearly always the result of their lazy programming style.



  • Not only that, but Optional<T> can also be null. (Unless Java 8 introduced a primitive that violates Java naming conventions) So you have two kinds of null instead of just one.



  • Wow, I forget to consider this. I imagine people who don't get the point will do just that - return null instead of empty optional.


  • Winner of the 2016 Presidential Election

    @anonymous234 said:

    What writing system is there that's not in Unicode?

    It's not necessarily an entire writing system - it might just be a set of glyphs or characters or what-have-you. Unicode.org alludes to the problem:
    [quote=" Unicode.org"]
    ###Q: Isn't it true that some Japanese can't write their own names in Unicode?
    A: There are some situations where an individual prefers their name be written with a specific glyph, as in the West we have John and Jon, Mark and Marc, Cathy and Kathy. In most cases, variation sequences in the UTS# 37 Unicode Ideographic Variation Database can be used to provide the required representation in plain text. In other cases, the variant forms have been encoded in Unicode as distinct characters. The IRG also may consider where the encoding of new variant characters is justified.

    It should be noted that this is not a problem of Han unification per se, as it is often represented. Unicode is a superset of the major Japanese character encoding standards. The various JIS standards and ISO 2022-based encodings have the same limitation. [JJ]
    [/quote]

    And here's a random blog post on the subject:
    I Can Text You A Pile of Poo, But I Can’t Write My Name



  • @Gaska said:

    If you do it like this, you explicitly agreed to crash if the name is empty

    And a lot of the time, that's fine, and actually what you want - especially with web applications, where crash recovery is relatively painless. Because most of the time unwanted nulls come from somebody fucking with your inputs,and you don't care about them getting an ugly 500 page.

    Sure, you can make the checks at compile time, and maybe in terms of language specification it can work... But then think about your data sources, and it quickly turns out a better half of it is optional, and you end up writing that million null checks anyway. Parsing JSON? You have to account for missing values. Getting data from the DB? Since we're talking objects and not primitives, joining on anything that's not a proper key forces you to either handle the nulls or drop whole records. Calling an external service? Well, it had a bad day and returned no response.

    Thing is, an empty Name field might not be what you want to deal with often, but that doesn't mean you won't have to somewhere. So either you drag the nullables around the whole application and your only benefit is klunky syntax, or you handle them as they come... but then there's usually nothing better to do than crash anyway.



  • @Dreikin said:

    we have John and Jon

    All the Jons I know of are comedians of one kind or another.


  • Winner of the 2016 Presidential Election

    @CoyneTheDup said:

    Remember, we're talking about people here too lazy to read the doc and do if (pwithoutoptional.getName() != null).

    Well, it's still better to return an optional (self-documenting interfaces, anyone?) than returning a reference and hoping that people actually read the text in the Javadoc to figure out it can be null.



  • I'd rather take the Go route, where nil is a valid value that can be used by code instead of just checked for or crashed-by-not-checking-for.

    For example, a nil slice (dynamic array) is a 0-length slice. A nil channel works like a channel with nobody on the other end. A string cannot be nil, but it can be empty.



  • Is there a way to tell apart a 0-length slice from nil?


  • Winner of the 2016 Presidential Election

    Why? Optional is very nice if it's built into the type system of the language, like in Kotlin. You don't have to give up null and replace it with a dummy instance of a type to create a language where NPEs are impossible.



  • Yes, you can compare slices for equality to nil.



  • That wasn't what I asked. My question was very specific.


  • Winner of the 2016 Presidential Election

    @ben_lubar said:

    For example, a nil slice (dynamic array) is a 0-length slice. A nil channel works like a channel with nobody on the other end. A string cannot be nil, but it can be empty.

    Also, sounds like what the Oracle database does 🚎



  • @LB_ said:

    Wow, I forget to consider this. I imagine people who don't get the point will do just that - return null instead of empty optional.

    Do you also worry about people returning null instead of an empty list?



  • @CoyneTheDup said:

    Coming up with nonsense like "Optional<T>" just kicks the can down the roadindicates what may be null, in a language where anything can be null

    Consider bool Dictionary<Key,Value>.TryGetValue(Key key, out Value value), what should it return in the second parm if not found?

    Should we

    1. Create the value anyway. And remember we can't store NULL there. Store an empty class, with a variable to indicate not-initialized
    2. Use Dictionary<Key,Optional<Value>> instead. For an interface where we know NULL is a reasonable result? And for methods that indicate success with bool.
    3. Have duck-typing like Javascript, but instead, retroactively act as if bool Dictionary<Key,Value>.TryGetValue(key) was called instead, and decide that at run time. And you still have to have a reference to the uninitialized object. (This one is just for the absurdity of it).
    4. Implement C++11 move style initialization. Then you don't have the actual reference. So, that's not a solution.
    5. Force people to always call and check bool Dictionary<Key,Value>.ContainsKey(Key key) first? This is by far the most sensible solution, but just adds in more syntax for the same result.


  • @Salamander said:

    Do you also worry about people returning null instead of an empty list?

    No, because none of the list classes have Optional in their name. My point is that I fear people will misunderstand how to use Optional<T> and take its name to mean "I can just return null if there's no value! Other people will know!"



  • @LB_ said:

    "I can just return null if there's no value! Other people will know!"

    Wait, what? What other purpose would Optional<T> serve other to indication non-value result?



  • There's a huge difference between a reference of type Optional<T> which is null and a reference of type Optional<T> which points to an empty Optional<T>



  • @ben_lubar said:

    All the Jons I know of are comedians of one kind or another.

    You know nothing Ben L


  • Winner of the 2016 Presidential Election

    Actually, after thinking about it for a while, I think Go's approach is actually the worst one.

    Let's go back to the person example. Each person can optionally have a spouse. Someone now writes some code that just calls getSpouse() and does something with the returned Person object. Here's what happens:

    • In Kotlin (and some other modern JVM languages), the code doesn't even compile. Awesome, the programmer gets immediate feedback that his assumption that every person has a spouse is wrong.
    • In Java, a NPE is thrown. Not as good, but at least it prevents the buggy code from doing stupid things.
    • In Go, the program will not crash and potentially do a lot of stupid things with the dummy Person objects that don't even exist. (*) Since the program compiles and doesn't crash, the error might go unnoticed for years.

    (*) I don't know whether this particular example would work this way in Go, but you get the idea.


  • Winner of the 2016 Presidential Election

    @RTapeLoadingError said:

    You know nothing Ben L

    Hi Jon!



  • @asdf said:

    In Go, the program will not crash and potentially do a lot of stupid things with the dummy Person objects that don't even exist. (*) Since the program compiles and doesn't crash, the error might go unnoticed for years.



  • I don't see how that demonstrates the scenario that @asdf was talking about?





  • I think https://play.golang.org/p/jtweYhdFU1 is closer to what we mean, though obviously default constructing an empty string isn't the same as saying the person doesn't have a name.






  • Winner of the 2016 Presidential Election

    @ben_lubar said:

    the Go route, where nil is a valid value that can be used by code instead of just checked for or crashed-by-not-checking-for.

    For example, a nil slice (dynamic array) is a 0-length slice. A nil channel

    There, I even read the Go documentation just to write some code to prove my point. This code should either crash or not compile, because it assumes that everyone invited has a spouse.

    Edit: Well, actually, the problem is that you apparently cannot even express that the spouse is optional in Go, or can you?



  • The Spouse string gets default constructed in your example. Same issue as my example.

    EDIT: http://stackoverflow.com/q/9993178/1959975 hm.


  • Winner of the 2016 Presidential Election

    I know, see my edit. Since Go doesn't have null or optional, it doesn't even allow me to express that people can have a spouse or not have one.

    Edit: http://play.golang.org/p/WsVie4UwOi is the best I can do to express that fact. No compile error, no runtime error when the code just ignores that Spouse can be null/nil.



  • You can have a *string if you really need null strings.


  • Winner of the 2016 Presidential Election

    Ok, but the more I learn about Go here by trying it out, the more I wonder why you previously said:

    @ben_lubar said:

    I'd rather take the Go route, where nil is a valid value that can be used by code instead of just checked for or crashed-by-not-checking-for.

    It seems like your statement was misleading and Go does, in fact, have null pointers that cause NPEs like most other languages. I was trying to argue that disallowing null and replacing it with default values is the worst possible solution to the problem that you occasionally have to deal with null/optional values. My point still stands, although it apparently doesn't apply to Go.


  • Discourse touched me in a no-no place

    @CoyneTheDup said:

    NULL is a halfway decent solution for messiness. Deal with it.

    There are other ways to represent that sort of complexity, such as completely omitting the value from the column/value map (while still saying that it can be present). NULL is only mandatory if you insist on indexing into the result set's row by column number (chosen from a compact sequence).


  • Banned

    @CoyneTheDup said:

    Remember, we're talking about people here too lazy to read the doc and do if (pwithoutoptional.getName() != null).

    I'm not. Those people are too stupid to be let into the office kitchen, let alone the codebase. I'm talking about real programmers, who are good at what they're doing, but always forget things.

    @ben_lubar said:

    Not only that, but Optional<T> can also be null. (Unless Java 8 introduced a primitive that violates Java naming conventions)

    Java 8 introduces @NonNull annotation. Though personally, I'd prefer something like @Strict annotation at package level rather than having to repeat the same annotation over and over again everywhere in all my files (@Strict reduces it to just once in every file; not ideal, but bearable).

    But as I said, with nullable references, optional type gives you nothing.

    @Maciejasjmj said:

    And a lot of the time, that's fine, and actually what you want - especially with web applications, where crash recovery is relatively painless. Because most of the time unwanted nulls come from somebody fucking with your inputs,and you don't care about them getting an ugly 500 page.

    When you really meant it, crashing on empty value is perfectly fine. The problem starts when you didn't mean it.

    @Maciejasjmj said:

    Sure, you can make the checks at compile time, and maybe in terms of language specification it can work... But then think about your data sources, and it quickly turns out a better half of it is optional, and you end up writing that million null checks anyway. Parsing JSON? You have to account for missing values.

    If the JSON is of known format, 99% of times you should refuse to deal with it at all if it doesn't have all the required fields. This reduces million null checks to just one - did parsing succeed or not.

    @Maciejasjmj said:

    Getting data from the DB? Since we're talking objects and not primitives, joining on anything that's not a proper key forces you to either handle the nulls or drop whole records.

    And with regular nullable references, that's different how?

    @Maciejasjmj said:

    Calling an external service? Well, it had a bad day and returned no response.

    You don't want to crash when your bundled spyware fails to retrieve UID from server, do you?

    @Maciejasjmj said:

    Thing is, an empty Name field might not be what you want to deal with often, but that doesn't mean you won't have to somewhere. So either you drag the nullables around the whole application and your only benefit is klunky syntax, or you handle them as they come... but then there's usually nothing better to do than crash anyway.

    In desktop applications, crash is almost never a good option. So while it might make little sense in webdev (I have some objections, but then I never written any weby stuff), it makes a whole lot of sense in the world where C#, Java and C++ are dominant languages.

    @asdf said:

    Optional is very nice if it's built into the type system of the language, like in Kotlin.

    Rust has great optionals too even though they're not built-in at all.

    @Salamander said:

    Do you also worry about people returning null instead of an empty list?

    Have you ever wondered why the IsNullOrEmpty() function was made?

    @xaade said:

    Consider bool Dictionary<Key,Value>.TryGetValue(Key key, out Value value), what should it return in the second parm if not found?

    It shouldn't be bool Dictionary<Key,Value>.TryGetValue(Key key, out Value value) to start with. It should be Optional<Value> Dictionary<Key,Value>.GetValue(Key key).


Log in to reply