4 ms·
I agree that union-typed null was the best solution for Dart, but I think many of the article's criticisms of option types aren't good. > We can’t perform arit
by creata 6y ago
I agree that union-typed null was the best solution for Dart, but I think many of the article's criticisms of option types aren't good.
> We can’t perform arithmetic on an Option<int> any more than we could on a List<int>.
Why not? One sensible definition is
Some(x) + Some(y) = Some(x + y)
_ + _ = None()
> In fact, with Dart, we’ve found that most existing code is already statically null safe
This is a very good reason to use union types — lots of programs and lots of people are used to thinking this way, and pulling that out from under their feet wouldn't be very nice.
> Nullable types, since they have no explicit runtime representation, are implicitly flattened.
Yes, flattening things that shouldn't be flattened is the biggest problem with union types. Different people will assign different meanings to null, and it's a real headache when trying to write generic code. Discriminated unions make this a no-brainer.
> With nullable types, since there is no representation difference, you can pass a value of the underlying type directly: takesMaybeInt(3);
That's a very minor difference, but if it's that important, Rust lets you write
fn takesMaybeInt(x: impl Into<Option<i32>>) { ... }
- gnosek 6y agoI agree with all your other points but > > We can’t perform arithmetic on an Option<int> any more than we could on a List<int>. > Why not? One sensible definition is (...) It's not the only sensible definition. It would be equally sensible to implement it as: Some(x) + Some(y) => Some(x+y) Some(x) + None => Some(x) None + Some(y) => Some(y) None + None => None
- creata 6y ago> It's not the only sensible definition Right, which is why I said it's just one sensible definition. :) Although I guess there's at least two sensible definitions of arithmetic on List<int>, too.
- runarberg 6y ago> > > We can’t perform arithmetic on an Option<int> any more than we could on a List<int>. Is actually kind of a weird point to make. There are number of ways to add together List<int> including: listA.zip(listB).map(([a, b]) => a + b); Similarly adding together two optionals could be done with: optionalA.zip(optionalB).map(([a, b]) => a + b); Or if you would rather: optionalA .zip(optionalB) .map(([a, b]) => a + b) .or(optionalA) .or(optionalB)
- int_19h 6y agoIt really depends on what the meaning of None is. OP's definition makes perfect sense if it means "unknown". Yours make sense if it means "zero", but that's far less useful. If it means "missing" - which is what the article says they wanted - then any operation involving None should be an error (but Some can still be handled automatically). It can be interesting to see what other languages do in this situation. For example, C# got nullable value types in version 2, and had to decide what to do with operators in a similar vein (including pre-existing overloaded operators in user code) - they called it "lifting": https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-value-types#lifted-operators https://docs.microsoft.com/en-us/dotnet/csharp/language-refe... https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/boolean-logical-operators#nullable-boolean-logical-operators https://docs.microsoft.com/en-us/dotnet/csharp/language-refe... You might notice that it almost, but not quite, has consistent semantics: null means "unknown", which is particularly obvious from Booleans: (true | null) is true, and (false & null) is false, but (true & null) and (false | null) are both null. However, comparison operators aren't consistent: you'd expect x == null to be null if it meant "unknown", but the language will always give you either true or false; and ditto for relative comparisons. This last one means that it's possible for (x == y), (x < y), and (x > y) to all be false. What's interesting is that, in practice, it's rare to see reliance on any of this behavior in idiomatic C# code - "null" is usually used as "missing", not as "unknown", so all this magic is, at best, irrelevant, and at worst, actively harmful (because it defers a logic error).
- gnosek 6y ago> It really depends on what the meaning of None is. Exactly. This will generally be application specific so we can't choose The One Right Definition for adding `Option<i32>`s. > Yours make sense if it means "zero", but that's far less useful. Not in my experience but I agree there are different situations. Personally, I'd default to `Option<i32>::None` meaning "no number there, so just skip it"[1] and prefer `Result<i32, E>::Err` to denote "there should be a number here but we don't have one", which would behave like a SQL NULL (propagate in all calculations). 1. What's the length of data in a header-only packet? I wouldn't say it's zero, especially when trying to fit a read() like API on top of a framed protocol (read() returning Ok(0) marks the end of file).
- munificent 6y ago> Different people will assign different meanings to null, and it's a real headache when trying to write generic code. This is a legitimate concern, but my experience has been that it is a surprisingly rare problem in practice. We software engineers are trained to believe that any system that supports 1 of something is obligated to generalize and support N of them. But you'd be surprised how far just one sentinel value gets you. I mean, one of the key observations underpinning having nullable (and thus non-nullable) types is that most places in your code need zero of these sentinel values. Most type, around 90% the last time I tried to count, don't permit null at all, so needing two distinct absent values is quite rare.
- creata 6y agoYeah, it is a rare problem, but it's not that rare when writing anything generic. For example, look at all the special-casing of null (ctrl-f "null") in the Java HashMap docs.[0] All of that would immediately go away if they didn't have null. [0]: https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html https://docs.oracle.com/javase/8/docs/api/java/util/HashMap....
- int_19h 6y agoThe bigger problem in generic code is that flattening means that you can't distinguish between incoming nullable values (that you don't know are nullable, because it's a type parameter!) that are null, and nulls in your own code.