7 ms·
Since this post is illustrating the use of generics, why not go all the way with the get implementation to return an Option[V] type? It's a natural thing to do
by malcolmgreaves 6y ago
Since this post is illustrating the use of generics, why not go all the way with the get implementation to return an Option[V] type? It's a natural thing to do here. The return type is already a kind of sum type: it's either the value you want (non-zero value, true) or it's not (zero value, false). If the implementation uses the optional type, it'll become impossible to write code that uses the zeroed-out returned value incorrectly. Calling code must always explicitly check the returned Optional[V] value to access the value of V and continue or to perform some code to handle the not present case. As it stands, it's very possible to ignore the 2nd returned boolean value and write code that'll easily break.
Now, I can see why the author would _not_ want to do this, since this "explosion" of sum-typed things is present in all go code (e.g. the err := ...; if err == nil { ... pattern). So, it might be easier for Go programmers to see how they could use generics in their own code by re-using this pattern. However, I think this is a disservice to why generics are an incredibly useful construct in programming languages. They can be used to align code more closely with the semantics that the programmer wants to convey.
- cy_hauser 6y agoI'm not understanding this. Are you saying an Option[V] would reduce the number of lines of code (the explosion) that Go code uses for "err := ...; if err == nil"?
- jerf 6y agoI don't think it would in Go. You'd still end up with result := ThingThatReturnsOption(...) if result.Error() != nil { // ... } happening in general, or some other equivalent construct. The main point of having Option in this case would be to make it so that where Go programmers normally write result, err := ThingThatMayError() you can get precisely one of a result or an error. At the moment with the current calling convention it is possible to both return a result and an error. However, I will say that while in theory this is advantageous (and I mean that seriously), in practice this is nearly a non-issue. I don't think I've ever had a bug because I had both things and misused them. I expect a dozen or more "Option" implementations to pop up nearly overnight once this is released, and for Go programmers to settle pretty quickly on not using it. In non-Go languages, Options can have additional features that make them yet more powerful, such as chaining together optional computations in a way that makes it easy to shortcircuit whole computations, e.g., in Haskell: do x <- optionalFail y <- somethingElseFail x z <- moreMightFail x y return (extractFromZ z) In Haskell, assuming the right definitions of the various functions, while that may look like it's not handling errors, it actually is, because the machinery behind their Option type (called Either in Haskell) is handling all the short circuiting. Go comprehensively lacks the features necessary to make that sufficiently pleasant to use that anyone will, though. It is not unique in lacking those features, most languages are missing at least one thing to make it easy enough to use people will, but it does quite comprehensively lack them. It's not just a matter of adding this one little thing or that other thing, it'd be a whole suite of necessary changes, e.g., you might be able to write an .AndThen(...) function to operate on a Option type, but it's going to be too inconvenient to use, even post-generics, and even if you force it because it's the Right Thing to Do in languages that aren't the one you are currently programming in, it's still going to be a lot of disadvantages for not much advantage. Personally I don't value "Doing the Right Thing in language X while working in language Y" very highly, but some people seem to.
- deleted 6y ago[deleted]
- Someone 6y agoI think they are saying using sum types will remove a source of errors (forgetting to check for error conditions). I also think part of their remark is on ‘either’, not ‘option’, but that’s not important for the point being made.
- alkonaut 6y agoWhen you chain multiple things that can go wrong with Result[T,E] or Option[V] it should be possible assuming there are methods for chaining/fallback. E.g. opening a file, reading the contents, parsing the contents etc. If all those 3 return Result[T,E] and you want the overall result (parsed) T or the first error to occur, then you should be able to chain that.
- masklinn 6y ago> If the implementation uses the optional type, it'll become impossible to write code that uses the zeroed-out returned value incorrectly. Since Go doesn't have sum types, it would most likely be possible: the option type would just be a reification of the MRV. At best it could panic if you try to get the value of an "empty" optional but now you've got a panic.
- alkonaut 6y agoIt’s always possible, even in a Rust Option you’ll panic when you extract the value with unwrap() without knowing that it’s valid. What it does is preventing the accidental use of a missing value. You can’t pass the Option<T> on to a function taking a T without explicitly doing it.
- weberc2 6y agoThis doesn't add anything. You get the same protection in Go with pointers. For example, a `*T` can't be passed to a function taking a `T` without explicitly dereferencing it. The problem of course is that the type system doesn't guarantee that the pointer isn't nil when you go to dereference it, similarly your `Option<T>` doesn't guarantee that the option.Value is set correctly. You need sum types to provide this substantial guarantee.
- alkonaut 6y agoIt guarantees that the value is set if the flag is saying it is set (that's the invariant of the type). It screams "check flag before accessing the value". To compare with references which are also 0-or-1-thing effectively: A reference where you as a developer know it's never null but always a ref to exactly one thing is denoted "* T" and a reference to "one thing or null" is denoted "* T"! There is no difference in the types! so you can accidentally send one that is "0-or-1-things" to a method accepting a * T that MUST be a thing. Type system didn't help you document which case it was. Options, apart from the annotation benefit it also helps making the syntax nicer in many cases, with e.g. "or()" fallbacks etc. let data = get_cached().or(load_from_disk()).or_panic();
- simias 6y agoI don't use Go myself but knowing its philosophy I wonder if they'll end up replacing the idiomatic "if err == nil" with an generic optional type even if they end up implementing generics in Go. For one thing it would generate a massive amount of churn to upgrade existing code, and if you don't update you'll quickly end up with very ugly mixed error handling patterns. On top of that Go seems to really value compilation speed, so I suspect that they won't want generics "contaminating" interfaces all over the place only to do error handling. I'm really curious to see (from the outside) how all of this is going to coalesce in the end.
- weberc2 6y agoGenerics aren't the gap (multiple return values are already generic), but rather sum types. Sum types are what allow you to express that this is either None/Sum(T) (Option) or Ok(T)/Err(E) (Result) or Nil/Cons (List) or etc.
- Groxx 6y agoSum types are what give you compile-time safety over those options. Run-time safety and developer-intent-signaling is entirely feasible with just generics.
- weberc2 6y agoRight, but as previously mentioned, Go's multiple return values are already "generic" and already signal developer intent. If you have a library method that returns (int, err) every single Go developer will check the err first before using the int. User-defined generics don't improve this use case.
- Groxx 6y agoResult types can, at runtime, by making the err case panic if the value is accessed, instead of just returning a zero value. They let you move past intent and into enforcement. Multiple returns are nothing but intent, and cannot be made stronger. You can do that without generics, of course. But the developer overhead is large enough that it effectively does not happen, as you have to redo that by hand for every type. That's what generics bring - ergonomics good enough to stop using less safe workarounds (e.g. `interface{}`, multiple returns).
- mdlayher 6y agoI like the idea, I just haven't given it a try with the new draft yet. Sounds like it's worth exploring at the very least.
- malcolmgreaves 6y agoCool! I'm glad you read my comment. I appreciate that you went through the effort to make a blog post (my comment is very low effort in comparison). I hope it came off as constructive.
- peter_l_downs 6y agoTake a look at the comment tree starting here in the Generics announcement thread from the other day for some discussion of Option[V] under the current proposal: https://news.ycombinator.com/item?id=23545361 https://news.ycombinator.com/item?id=23545361 For what it worth, returning `(value, err)` is conceptually the same thing as returning a Result[V]. You can ignore the error case on a result / the None case on an option as easily as you ignore the `err` today.
- MHordecki 6y ago> You can ignore the error case on a result / the None case on an option as easily as you ignore the `err` today. The point of a result type is that you _cannot_ ignore the None case. Any method that would provide you with the value will also check that the error is not present. In comparison, you can happily ignore `err` in Golang and continue with an invalid `value`.
- weberc2 6y ago> In comparison, you can happily ignore `err` in Golang and continue with an invalid `value`. I'm struggling to envision a Result type that requires you to be more explicit than `foo, _ := fallible()`. Seems like `fallible().Ok()` and similar are strictly less explicit.
- hombre_fatal 6y agoTo do anything with a Result/Optional, you have to explicitly get the value inside the box at some point. But the container abstraction also gives you a nice composition abstractions instead of the uncomposable top-level `val, err := do()` destructure. Though perhaps not quite as compelling without real sum types. I'd have to play with Go's generic-typing sandbox more to form a stronger opinion.
- deleted 6y ago[deleted]
- peter_l_downs 6y ago
- ccktlmazeltov 6y agoI really wish Go would pursue sum types instead of generics
- mrath 6y agoSum types without generics will still be good enough? I think for things like Option/Result we need both sum types and generics.
- ccktlmazeltov 6y agoI don't think you would need user generics for this to work.
- masklinn 6y agoNon-generic sum types are useful but you can’t build an option or result type out of them, so the usefulness is somewhat hampered.
- ccktlmazeltov 6y agoBut you don't need to, generic Option and Result types could be built as part of the standard library.
- masklinn 6y agoYes but then you need neither sum types nor user-defined generics, you can just build-in whatever sugar is useful or necessary (possibly repurposing existing sugar e.g. the `select` statement).
- ccktlmazeltov 6y agosum types are still super useful imo without generics.
- 6y ago