logoalt Hacker News

_fluxyesterday at 9:48 AM6 repliesview on HN

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.


Replies

maccardyesterday at 4:08 PM

> 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
gm678yesterday at 2:36 PM

Different language, but I find this Kotlin RFC proposing union types has a nice canonical example (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.
ecedenoyesterday at 11:52 AM

Your 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.

show 1 reply
lock1yesterday at 11:41 AM

Well, 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.

show 1 reply
skydhashyesterday at 12:58 PM

I 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)

show 2 replies