5 ms·
I'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
by ablob 2y ago
I'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?
- nyssos 2y agoThe Haskell equivalent `c <$> b <$> a` is roughly as concise.
- 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