6 ms·
If it really can't ever be null, then it should just be an int, not an Optional<int>. The entire reason that it is an Optional<int> is that it CAN be null. In
by kerkeslager 7y ago
If it really can't ever be null, then it should just be an int, not an Optional<int>. The entire reason that it is an Optional<int> is that it CAN be null.
In this hypothetical language, not initializing an int is a compiler error, assigning null to an int is a compiler error, etc. If it's an int it literally cannot be null.
What ends up happening in practice is that the null is handled close to where it's created, and the rest of the code passes around an int you know isn't null, because people don't feel like passing around an Optional<int> and being forced to check it everywhere.
Sure, you can intentionally write code that does the wrong thing in any language, but the "return 0;" would be a very obvious error in even cursory code review.
- tsimionescu 7y agoThere are normal cases where this can happen. For example, a map should normally return an Optional<ValueType> when you try to retrieve a key's association. However, there may be special cases where you know that a key is present (maybe it is a constant map, maybe you just set the value of that key etc). I do agree that these cases are much rarer than the cases where a value is either always there, or the cases where a value really can be missing. I was only pointing out that Optional doesn't eliminate 100% of null errors, just 99.9% of them.
- kerkeslager 7y ago> For example, a map should normally return an Optional<ValueType> when you try to retrieve a key's association. However, there may be special cases where you know that a key is present (maybe it is a constant map, maybe you just set the value of that key etc). Both of these cases are still pretty big code smells. 1. Just don't use constant maps. Instead of doing this: const myConfig = new Map<string, int> { "height": 72, "weight": 160, }; [...] var height = myConfig["height"].matchOptional( ifNull: () => 0, ifValue: i => i, ); ...use a constant structure (with an anonymous type): const myConfig = struct { height: 72, weight: 160 }; var weight = myConfig.weight; You can verify whether height or weight are null at compile time this way[1]. 2. If you just set the value of the key, instead of doing this: dictionary[word] = getDefinition(); let definition = dictionary[word].matchOptional( ifNull: () => "", ifValue: d => d, ); ...do this: let definition = getDefinition(); dictionary[word] = definition; I'm aware that I'm playing fast and loose with the syntax of our pseudo-language, but note that avoiding the optional will is terser and simpler than using the optional and eating the null case--this is true in most cases in most strongly/statically-typed languages. Not only do you learn to lean on the type system in a strongly/statically-typed language, but if the syntax is well-designed, it makes it easier to lean on the type system than to not lean on the type system. [1] You may say, but what if I'm loading from a file? The common pattern is to load a config from a file as a map, and then load it into a struct, setting defaults, like so: const defaultHeight = 72; const defaultWeight = 160; JsonObject configJson = json.loadFile("config.json"); const config = struct { height: configJson["height"].matchOptional( ifNull: defaultHeight, ifValue: v => v.asInt(notInt: v => throw Exception("Invalid height \"{}\" in config.".format(v))) ), weight: configJson["weight"].matchOptional( ifNull: defaultWeight, ifValue: v => v.asInt(notInt: v => throw Exception("Invalid weight \"{}\" in config.".format(v))) }; You eventually hit cases with user data where you can't handle it (hence the throwing exceptions) but this pattern allows you to fail early, and with descriptive error messages.