3 ms·
There would be two major problems with sum types (especially for results) in Go 1.x: - There are genuine use cases where returning both a value and an error ma
by rollcat 3y ago
There would be two major problems with sum types (especially for results) in Go 1.x:
- There are genuine use cases where returning both a value and an error makes complete sense. My favourite example is doing I/O: some bytes were copied, but there was a problem with the rest.
- It has significant overlap in scope & functionality with interface types. You could have "type Color enum { Named(string); RGB(int, int, int) }", or you could have "type Color interface { RGB() (int, int, int) }; type NamedColor string;" etc and it's not very clear which style you'd be supposed to use, and in what case.
The first problem could be somewhat alleviated by having very special result types that can have both a return value and an error, but these would have to be different from regular "variant" results, so you'd end up either with two ways to do a very similar thing, or yet another layer of abstraction.
The second problem is fundamental to the design of the language, and I would absolutely *hate* to see Go go through the same unholy mess as Python: "old-style" vs "new-style" classes, dataclasses (with third-party "attrs" as a stepping stone), protocols/generic containers, "match"/destructuring, all of these were clumsily grafted on and code that mixes all of these styles can be found all over the place. You do need to make ADTs and pattern matching a first-class citizen in 1.0, otherwise the place you end up in is not pretty at all.
Go? Yeah, we have a lot of SortIntegers([]int) and SortStrings([]string) and SortByName([]Person) in older code, but that's about its biggest sin. As of 1.21, slices.Sort & slices.SortFunc are in std and fixing this older code is pretty much a mechanical task. It's not perfect, but at least it's not worse by trying too hard to be perfect.
- the_gipsy 3y ago> My favourite example is doing I/O: some bytes were copied, but there was a problem with the rest. How do you reconcile that with the general rule or convention that `if err != nil` the value is unsafe to use? Apart from this, I don't think sum-types should be added this late either. I should have written "if they had implemented". "old vs new style" does more damage than good. Today, it is what it is. What I do think is feasible is adding some more syntax sugar around errors.
- bheadmaster 3y ago> How do you reconcile that with the general rule or convention that `if err != nil` the value is unsafe to use? By reading the documentation: https://pkg.go.dev/io#Reader https://pkg.go.dev/io#Reader Conventions are not laws, and should not be followed blindly. > What I do think is feasible is adding some more syntax sugar around errors. The most common example I hear is something like Rust's `?` operator for propagating errors. In Go, errors should almost always be expanded with additional context, and programmers should think hard about errors and how to handle them. Syntax sugar would just make it easier for people to ignore errors. And you can't ignore errors forever - they always come up, most often in the place you least expect them.
- the_gipsy 3y ago> In Go, errors should almost always be expanded with additional context I'm convinced this is just a very poor way to cope with the lack of stacktraces.
- bheadmaster 3y agoActually, stack traces are a very poor way to provide context. I can't count how many times I've worked with some library in ${LANGUAGE_WITH_EXCEPTIONS}, and had an exception print out a giant stack trace, only for me to realize that the stack trace is useless because the place where exception is raised isn't the place where the error actually happened, but is actually some wrapper/worker collector/other kind of indirection mechanism. In other words, stack traces are directly bound to call stacks, and call stacks don't necessarily contain all relevant context of an error - they only do so in case of simple, single-threaded programs. Golang-style error-as-values actually provide real, human-curated context that is relevant to the operation at hand. They can be passed between goroutines, and are completely independent of any call stacks. That, in my opinion, makes them vastly superior to stack traces.
- the_gipsy 3y agoIt's true that stacktraces don't work out so well in many languages. But ironically in go they do seem to work fine in my experience. Adding a stacktrace to custom go errors do show me correct and useful traces across goroutines. I guess it's the fact that async is built into the language as a primitive or something. Regarding wrapping errors, if you don't provide a stacktrace or at least file:line, how do you actually map the error to source code? Do you just grep and pray that the message is unique enough?
- 015a 3y ago> There are genuine use cases where returning both a value and an error makes complete sense. I don't feel anyone is saying that the addition of sum types would have to replace the existing (Result, error) pattern. Its just a tool in the toolbox. In fact, I think having both is really interesting from a function signature communication perspective; if a function returns (Result, error) in a post-sum-type world, that hints to me that the Result might still be useful even if an error is returned. > I would absolutely hate to see Go go through the same unholy mess as Python: "old-style" vs "new-style" classes, Anytime someone brings up how horrible the Py2 to Py3 transition was, I will remind them that Python is the most popular programming language in the world. Clearly; the transition wasn't actually bad enough to negatively impact its popularity; yet its the token example of "don't break existing programs, you don't want to be like Python". That take is just counter-intuitively wrong. Its like the SDLC paradigm of "releasing often reduces bugs"; it feels wrong, but its actually right. I'm sure saying "we'll never break existing programs" imparts a nice warm feeling in your heart, "we're mature, unlike those other dumb languages". But there is extremely little evidence that backward-incompatible language changes hurt the adoption of programming languages. Actually; there's substantially more evidence that languages which can't adapt and evolve to change will eventually die. I'm not asserting that will happen to Go; I think they're good about bringing forward enhancements to the language in ways that don't break existing programs (like generics). But I also strongly believe they need to rethink their "we won't break existing programs" rule. I am begging the Go team to break my programs. Its not a big deal. I just can't imagine still writing Go programs in 2050 like we are today; it leaves so much value on the table, and we can do better.