7 ms·
I think the nullable reference type system in c# is a perfectly workable compromise for languages that had nullability from the very beginning. Once a code base
by m_fayer 2y ago
I think the nullable reference type system in c# is a perfectly workable compromise for languages that had nullability from the very beginning. Once a code base uses them fully, most null-related bugs are gone. And it’s far from an unwieldy bolt-on.
- aloisdg 2y agoThe Nullable class is nice and I love to use it but it would be even better with an option type like in F#
- alkonaut 2y agoThere isn’t any big difference between T? And Option<T> semantically. Many code bases use an Option<T> in C# to indicate a 0-or-1 object result, but refactor those to T? instead with nullable. It combines better e.g Option<Option<T>> doesn’t need to be handled manually.
- ablob 2y agoI'm going to be a bit pedantic here: There is a semantic difference between Option<Option<T>> and Option<T>. If I intend to retrieve a setting from a file, the former allows me to differentiate between a missing file or a missing setting, while the latter destroys that information. i.e.: There are 3 possible cases, while only 2 can be represented. So T? doesn't compose, while Option<T> does, which I'd consider a big difference. However, without a builtin option type, duck-typing, or a pleasant way of converting the types, Option<T> may become a hassle (especially when different dependencies ship their own). And as T? is shipped with the language this is probably why it is used when the composability is not required. P.S.: In C# T? even neatly composes. return obj?.a?.b is equivalent to if (obj != null && obj.a != null) { return obj.a.b } else return null;
- zigzag312 2y ago> There is a semantic difference between Option<Option<T>> and Option<T>. If I intend to retrieve a setting from a file, the former allows me to differentiate between a missing file or a missing setting Does nesting Option's really has practical use or does it quickly become confusing? In your example, Option<Option<T>> return type doesn't tell me by itself that this differentiates between a missing file or a missing setting. I would need to get this information from somewhere else.
- Twisol 2y agoSometimes you'll have nested Options just because you're mapping a fallible operation over a fallible input. You don't want the resulting `Option<Option<T>>` to immediately collapse; then you wouldn't know which upstream operation failed. It's true that `Option<Option<T>>` is very generic (i.e. it doesn't inherently tell you what each None means), but flattening Options removes more information; it isn't a solution to the problem you're posing. At least you can post-process an `Option<Option<T>>` into a multi-variant, self-documenting result type before you pass the value off to some other consumer. In other words, nested optionals might not be very readable, but they're a necessary product of having a highly modular, reusable toolkit, and you can always massage them into more informative, domain-specific types at whatever point that becomes appropriate.
- dwaite 2y agoThe biggest impact is on generic code - you don't want supplying Foo? as T rather than Foo to mean that there are side effects where successful returns and error cases are both represented by null.
- zigzag312 2y agoIn some cases (limited) nesting really seems useful. Nullable parameters for a Copywith method is another one (does null value mean 'no change' or 'set it to null'). The thing is that nullable covers majority of cases and is quicker to read and write (Foo? vs Option<Foo>). Doing a?.b?.c is also more elegant than anything equivalent using an Option type. Is there any languages that successfully combines both, nullable '?' syntax and an Option type?
- evrimoztamur 2y agoThat sounds like you should be using Result<T, E> to handle the two E cases you are describing. Success with Ok(T), with Err(Missing File) plus Err(Missing Setting).
- dwaite 2y agoProbably. A case I see more often is a dictionary with nullable values. A T? return can't distinguish key missing or key present with null value. Which leads to a bit of an argument - nesting gives poor ergonomics, while collapsing impacts the ability to write generic code.
- tubthumper8 2y agoWhich you can't do in C# either without sum types :)
- josephg 2y agoYeah - this is something I noticed coming to Swift after spending a bunch of time in rust. Swift has T? syntax, and rust has Option<T>. The Swift syntax felt much more “lived in” - like, nullables were much easier to use and I found myself using them more. Rust has dozens of helper methods for option - like map, map_or, map_or_else, and so on. When you’re starting out, it’s quite hard to find what you want in the morass of options. And many of the helper functions take a closure, which messes up your ability to do control flow (return, break, continue) from the containing function. In Swift, like typescript, I found the syntax sugar around options to be much easier to learn. Eg obj?.a?.b rather than checks docs obj.and_then(|o| o.a).and_then(|a| a.b). “If let” in rust helps. (Similar to guard let in Swift) Ie, you can write if let Some(x) = x { … }. But you still can’t combine that with other conditions in the if statement - which drives me nuts. https://doc.rust-lang.org/std/option/enum.Option.html https://doc.rust-lang.org/std/option/enum.Option.html
- Spivak 2y agoI think Option<T> is an overrated construct when it's not a monad. The whole value is being able to write a chain of operations myobj.dothing(x).otherthing(y) Without having to check the intermediate results. When it's just a tagged union you end up having to check for null everywhere anyway. It's better than "untyped" null because it can't pop up anywhere but it's not super ergonomic. I think Nullable where the Option is implicit does it better.
- klysm 2y agoOne important distinction I didn't get at first is that Nullable<T> is a struct (not a class). It is only for value types. Nullable reference types are implemented via attributes.
- jeroenhd 2y agoWith a good IDE configured to break on nullability errors, @Nonnull and @Nullable can also be used to replace the C# system in practice (not the same, but close enough). Unfortunately, this requires coordination from different team members and external dependencies rather than standard language features.
- DSMan195276 2y agoIt's funny because I really dislike the C# approach. It's not truly enforced - any code with nullable disabled can call public functions with `null`, even if on your side they were marked as non-null.
- klysm 2y agoYup, unfortunately for us there is a lot of C# code written without NRT. If all of your code utilizes NRT and you respect the warnings, you can nearly eliminate issues related to null. There are some paper cuts around generics, but generally it has been working quite well for me. Our entire code base has NRT enabled and the only place where null sneaks in is with EF Core missing includes. This is actually kinda nice because if you get a null-whatever exception, 99% of the time it's a missing include.
- becquerel 2y agoThis is true, but you can fairly easily audit your solution for files/projects which have it disabled. And yeah, you need to verify the data at the boundaries of your trusted code - but that's the same situation as it's always been. Where NRT help is avoiding boilerplate null checks in the dense forest of your business logic.
- DSMan195276 2y agoWell my point is more that if they introduced an entirely new non-null reference type to the language then you could never make that mistake - it would be non-optional for those calling into your code to respect whether null can be passed in or not based on the type you used. Since they instead made it an optional system you can not rely on those using a library you make to actually turn it on, you have to accept that null _can_ be passed in even if you mark something as non-null according to the system on your side, which is dumb.
- sebazzz 2y agoYes, that is possible but you still get warnings and you should compile with warnings as errors anyway.
- DonHopkins 2y agoWhy stop at null, when you can have both null and undefined? Throw in unknown, and you've got a hat trick, a holy trinity of nothingness! Of course the Rumsfeld Matrix further breaks down the three different types of unknowns. https://en.wikipedia.org/wiki/There_are_unknown_unknowns https://en.wikipedia.org/wiki/There_are_unknown_unknowns >"Reports that say that something hasn't happened are always interesting to me, because as we know, there are known knowns; there are things we know we know. We also know there are known unknowns; that is to say we know there are some things we do not know. But there are also unknown unknowns—the ones we don't know we don't know. And if one looks throughout the history of our country and other free countries, it is the latter category that tends to be the difficult ones." -Donald Rumsfeld 1) Known knowns: These are the things we know that we know. They represent the clear, confirmed knowledge that can be easily communicated and utilized in decision-making. 2) Known unknowns: These are the things we know we do not know. This category acknowledges the presence of uncertainties or gaps in our knowledge that are recognized and can be specifically identified. 3) Unknown unknowns: These are the things we do not know we do not know. This category represents unforeseen challenges and surprises, indicating a deeper level of ignorance where we are unaware of our lack of knowledge. And Microsoft COM hinges on the IUnknown interface. https://en.wikipedia.org/wiki/Tony_Hoare#Research_and_career https://en.wikipedia.org/wiki/Tony_Hoare#Research_and_career >Speaking at a software conference in 2009, Tony Hoare apologized for inventing the null reference: >"I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years." -Tony Hoare https://news.ycombinator.com/item?id=19568378 https://news.ycombinator.com/item?id=19568378 >"My favorite is always the Billion-Dollar Mistake of having null in the language. And since JavaScript has both null and undefined, it's the Two-Billion-Dollar Mistake." -Anders Hejlsberg >"It is by far the most problematic part of language design. And it's a single value that -- ha ha ha ha -- that if only that wasn't there, imagine all the problems we wouldn't have, right? If type systems were designed that way. And some type systems are, and some type systems are getting there, but boy, trying to retrofit that on top of a type system that has null in the first place is quite an undertaking." -Anders Hejlsberg
- Kuinox 2y agoI think there is a way to migrate to an union type myType | null. Instead.
- eru 2y agoUnion types are pretty ugly. Eg they can't distinguish between one or two layers of nullable. Or between null and an optional null. (In eg Rust syntax between `Option<Option<MyType>>` vs `Option<MyType>`. Or `()` vs `Option<()>`, where `()` would be how you spell `null` in Rust.) Using tags is simpler and cleaner.