3 ms·
I personally don't enjoy the MyObject? typing, because it leads to edge cases where you'd like to have MyObject??, but it's indistinguishable from MyObject?. E
by _flux 9mo ago
I personally don't enjoy the MyObject? typing, because it leads to edge cases where you'd like to have MyObject??, but it's indistinguishable from MyObject?.
E.g. if you have a list finding function that returns X?, then if you give it a list of MyObject?, you don't know if you found a null element or if you found nothing.
It's still obviously way better than having all object types include the null value.
- lock1 9mo agoWell, in a language with nullable reference types, you could use something like fn find<T>(self: List<T>) -> (T, bool) to express what you want. But exactly like Go's error handling via (fake) unnamed tuple, it's very much error-prone (and return value might contain absurd values like `(someInstanceOfT, false)`). So yeah, I also prefer language w/ ADT which solves it via sum-type rather than being stuck with product-type forever.
- _flux 9mo agoHow does this work if it is given an empty list as a parameter? I guess if one is always able to construct default values of T then this is not a problem.
- maccard 9mo ago> I guess if one is always able to construct default values of T then this is not a problem. this is how go handles it; func do_thing(val string) (string, error) is expected to return `"", errors.New("invalid state")` which... sucks for performance and for actually coding.
- ecedeno 9mo agoYour example produces very distinguishable results. e.g. if Array.first finds a nil value it returns Optional<Type?>.some(.none), and if it doesn't find any value it returns Optional<Type?>.none The two are not equal, and only the second one evaluates to true when compared to a naked nil.
- _flux 9mo agoWhat language is this? I'd expect a language with a ? -type would not use an Optional type at all. In languages such as OCaml, Haskell and Rust this of course works as you say.
- _rend 9mo agoThis is Swift, where Type? is syntax sugar for Optional<Type>. Swift's Optional is a standard sum type, with a lot of syntax sugar and compiler niceties to make common cases easier and nicer to work with.
- _flux 9mo agoRight, so it's not like a union type Type | Null. Then naturally it works the same way as in the languages I listed.
- deleted 9mo ago[deleted]
- skydhash 9mo agoI like go’s approach on having default value, which for struct is nil. I don’t think I’ve ever cared between null result and no result, as they’re semantically the same thing (what I’m looking for doesn’t exist)
- onionisafruit 9mo agoIn Go, the default (zero) value for a struct is an empty struct.
- Cyph0n 9mo agoEh, it’s not uncommon to need this distinction. The Go convention is to return (res *MyStruct, ok bool). An Option type is a cleaner representation.
- gm678 9mo agoDifferent language, but I find this Kotlin RFC proposing union types has a nice canonical example (https://youtrack.jetbrains.com/projects/KT/issues/KT-68296/Union-Types-for-Errors https://youtrack.jetbrains.com/projects/KT/issues/KT-68296/U...) inline fun <T> Sequence<T>.last(predicate: (T) -> Boolean): T { var last: T? = null var found = false for (element in this) { if (predicate(element)) { last = element found = true } } if (!found) throw NoSuchElementException("Sequence contains no element matching the predicate.") @Suppress("UNCHECKED_CAST") return last as T } A proper option type like Swift's or Rust's cleans up this function nicely.
- maccard 9mo ago> E.g. if you have a list finding function that returns X?, then if you give it a list of MyObject?, you don't know if you found a null element or if you found nothing. This is a problem with the signature of the function in the first place. If it's: template <typename T> T* FindObject(ListType<T> items, std::function<bool(const T&)> predicate) Whether T is MyObject or MyObject?, you're still using nullpointers as a sentinel value; MyObject* Result = FindObject(items, predicate); The solution is for FindObject to return a result type; template <typename T> Result<T&> FindObject(ListType<T> items, std::function<bool(const T&)> predicate) where the _result_ is responsible for the return value wrapping. Making this not copy is a more advanced exercise that is bordering on impossible (safely) in C++, but Rust and newer languages have no excuse for it
- k_g_b_ 9mo agoWhen you want to distinguish `MyObj??` then you'll have to distinguish the optionality of one piece of code (wherever your `MyObj?` in the list came from) with some other (list find) before "mixing" them. E.g. by first mapping `MyObj?` to `MyObj | NotFoundInMyMap` (or similar polymorphic variant/anonymous sum types) and then putting it in a list. This could be easily optimized away or be a safe no-op cast. Common sum types allow you to get around this, because they always do this "mapping" intrinsically by their structure/constructors when you use `Either/Maybe/Option` instead of `|`. However, it still doesn't always allow you to distinguish after "mixing" various optionalities - if find for Maps, Lists, etc all return `Option<MyObj>` and you have a bunch of them, you also don't know which of those it came from. This is often what one wants, but if you don't, you will still have to map to another sum type like above. In addition, when you don't care about null/not found, you'll have the dual problem and you will need to flatten nested sum types as the List find would return `Option<Option<MyObj>>` - `flatten`/`flat_map`/similar need to be used regularly and aren't necessary with anonymous sum types that do this implicitly. Both communicate similar but slightly different intent in the types of an API. Anonymous sum types are great for errors for example to avoid global definitions of all error cases, precisely specify which can happen for a function and accumulate multiple cases without wrapping/mapping/reordering. Sadly, most programming languages do not support both.