Hacker News new | past | comments | ask | show | jobs | submit login

Maybe I am missing something here but as someone who is not an expert in go generics, I don't see any obvious "crimes" with the Option and Queue implementations.

I also could not find where the author explains their "crimes".

What I am missing here?




Not a Go person either but I think the joke with the queue is that Go channels are already exactly that - MPMC queue, and with "generics" baked into the language.

The Option is just a good idea and sum types should have been in the language from the beginning to handle errors instead of product types. But that ship has sailed and sunk.


An Option type without pattern matching is just as useless. Before unwrapping the value, you should check if its None and if you fail to do that, you get a panic. That's literally how pointers already work.


Java doesn't have pattern matching (not until the most recent releases, to be pedantic) and the Optional type has absolutely made my programming safer. You don't need pattern matching if you can use functions like `map` and `orElse` or `orElseThrow`, or just return the optional. I get 99% of what I do in OCaml with 'a Option with just these three functions.


Won't those methods still throw an exception if the object is null though? (Honest question, I have not done much Java since Optional was added and was under the impression that it didn't support implementing methods that wouldn't throw exceptions on null like Go can do with nil)


No, and I think you're misunderstanding how Java optionals work. They're just like any ML-type language's option type - a box which can either have something or nothing.

You choose if you want an exception. Map will only run if the optional contains a non-null value, so it won't throw. orElse will safely give you a value if the optional contains a null, so it won't throw either. The only time you throw is when you use orElseThrow, but that's in the name and you know what you're doing.

Ex.

    Optional<Integer> x = Optional.ofNullable(null);
    // This won't throw!
    x.map(i -> i + 1);
    // This won't throw either, and safeValue will *definitely* be an int!
    int safeValue = x.orElse(5);
    // This *will* throw, but you specify what to throw
    x.orElseThrow(() -> new RuntimeException())
These three methods cover nearly everything I did with options in OCaml as well, so I think that's about everything you need.


Right, but what about `Optional<Integer> x = null`? Is there any static check to make sure this can't happen, or is it something you have to just avoid? I can imagine someone might accidentally return `null` instead of `Optional.ofNullable(null)` in a method that returns an Optional, but maybe in practice catching this with a lint is enough.


> Is there any static check to make sure this can't happen, or is it something you have to just avoid? I can imagine someone might accidentally return `null` instead of `Optional.ofNullable(null)

The latter. Java's Optionals are half-baked and don't provide as much safety as you'd think because it's easy for a `null` value to slip in. Notice how they used `Optional.ofNullable` — a common footgun is using `Optional.of(value)`, which throws a NullPointerException if `value` is null[1].

[1] https://docs.oracle.com/javase/8/docs/api/java/util/Optional...


This is true, but I think half-baked safety is way better than no safety, and in practice I've found that it's actually pretty difficult for a null to slip in, because that only realistically happens when you're directly assigning values. Library code that returns optionals has never caused any issues IME, and custom code that returns optionals is super easy to review because there's only one good way to make an optional - ofNullable.


It’s just something you have to avoid. It’s not hard to avoid though, unless you are doing bizarre and non idiomatic things with Optional. I have never actually seen a NullPointerException resulting from a null Optional, in my code or my coworkers’, and I’ve been writing Java since their inception.


All the Java project I've been on, the convention is to always instanciate containers.


> That's literally how pointers already work.

With the huge difference that Go’s pointers don’t statically require checking if they’re `null`.

Furthermore, you can add functor and monadic APIs to `Option` which provide completely safe (and somewhat efficient) usage patterns even if you build the option out of product types.

Though that isn’t the case here, an other interesting item is that you can build an option type out of non-pointer, thus avoiding the indirection and allocation (though hopefully unlike the C++ committee you don’t do it just so you have a pointer without an allocation)


There is no meaningful null check enforcement like with pattern matching, if you try to access the Option value and fail to handle the error correctly, you either get a panic (from the following null deref) or in this blogpost the zero initialized value (which is honestly worse than an instant crash).

>Furthermore, you can add functor and monadic APIs to `Option`

There's nothing preventing you from defining these functions directly on pointers, they'd be just as safe.


Not the way it’s constructed, but there are safe patterns e.g. providing a callback which is only called when a value is present.

Whether or not that’s the way you want to program is a whole different question, but you can get a level of safety from constructs like this you can’t get from a pointer.


> [...] if you try to access the Option value and fail to handle the error correctly, [...]

It's consistent with the way Go's error handling works. Turns out it's not anywhere near as much of an issue as you might think. In practice you always notice that the function you're about to call returns an error and handle it.

> There's nothing preventing you from defining these functions directly on pointers, they'd be just as safe.

Pointers aren't Optionals. They happen to be nilable, yes, but their semantics are broader. A pointer can be used to avoid copying large structures around. How would you distinguish such a pointer vs one that's used as an Optional?


Not the way it’s constructed, but there are safe patterns e.g. providing a callback which is only called when a value is present.

Whetger or not that’s the way you want to program is a whole different question, but you can get a level of safety from constructs like this you can’t get from a pointer.


The slight advantage here is that the Option returns an error if the value is a zero value (which nil is). Existing linters will throw warnings about not checking errors. I don't believe I've seen a "not-nil" checker, although I could be wrong.


Actually, go generics can do sum types, it’s just not well known or advertised.


No, they can't. Or more precisely, only generic type parameters can be sum types, not actual runtime variables/parameters/return types/struct fields.


> not actual runtime variables/parameters/return types/struct fields.

Is it possible to church-encode these with Go generics?


Yes, probably, but I'm not sure that would be very ergonomic (I don't think you'd want to use it for something like Optional), if I understand correctly how it would work.


I was similarly confused. The patterns look very similar to those used in some other languages (at least superficially), so would have been useful to know why those were considered bad.

As a casual Go user, it wasn't obvious whether those particular implementations were bad, whether the patterns themselves are a bad fit for the Go language, or whether there was just a better (more Go-like) way of doing things.


I think the Option example (despite being the one the author thinks may actually be useful) is the worst one, since it really only replaces existing pointer syntax with named functions, but offers nothing else (well, maybe the Take() call?)

A better function to add to Optional[T] / *T would have been Map:

  func Map[T any, V any] (o *T, foo func (T) V) *V {
    if o == nil {
      return nil
    }
    v := foo(*o)
    return &v
  }
or, with Optional:

  func Map[T any, V any] (o Optional[T], foo func (T) V) Optional[V] {
    if o.IsNone() {
      return Optional[V]{}
    }
    return Optional[V]{foo(o.Yank())}
  }




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: