Live data from Hacker News

Golang proposal: container/: generic collection types

github.com

161–170 of 207 posts

Re: Golang proposal: container/: generic collection types

#161

Earlier quoted context omitted.

In the overwhelming majority of cases, you want to trivially send the error up the stack to be handled, so you don't want to incur the noise in the middle of your business logic (and indeed doing so obscures the times when error handling is non-trivial!). e.g. Scala handles all error cases with compile time checking (in fact it tends to take error handling much more seriously), but you don't have to write (or read) t…

> In the overwhelming majority of cases, you want to trivially send the error up the stack to be handled There is no case where you would ever trivially send the error up the stack. First, if your function doesn't have any meaningful information to add, you need to seriously question why the function exists. It is almost certainly not adding anything of value and the function that originally produces the error can ju…

Functions exist first to give names to conceptual blocks, and should generally be defined by "level of abstraction." i.e. you give a name to some task that then perhaps calls some low level data validation or manipulation functions which can return errors. Your business process might not care which task had an error, so it just bubbles up to the higher level coordinator (e.g. a handler just returning errors to a router).

You do not need to have new wrappers at every level. That's what interfaces are for. e.g. if all you're going to do is retry up to N times and log, and you don't need special handling, you just call String() or whatever. But you still want libraries to return domain errors so that you can handle them if you need to, or for tests, etc. And you can have some layer in the middle to make like SQLError or DatabaseError.

Right I'm saying Scala does exactly that, and has libraries that e.g. propagate stack traces across fibres automatically and invisibly. There's prior art to observe. It's not some mysterious unsolved problem.

Re: Golang proposal: container/: generic collection types

#162

Earlier quoted context omitted.

> In the overwhelming majority of cases, you want to trivially send the error up the stack to be handled There is no case where you would ever trivially send the error up the stack. First, if your function doesn't have any meaningful information to add, you need to seriously question why the function exists. It is almost certainly not adding anything of value and the function that originally produces the error can ju…

Functions exist first to give names to conceptual blocks, and should generally be defined by "level of abstraction." i.e. you give a name to some task that then perhaps calls some low level data validation or manipulation functions which can return errors. Your business process might not care which task had an error, so it just bubbles up to the higher level coordinator (e.g. a handler just returning errors to a rout…

> Functions exist first to give names to conceptual tasks

Right, and conceptual tasks will almost always have information that is usefully attached to an error. There are exceptions, but even in those exceptions you still have to ensure you don't leak implementation details, so you still cannot trivially pass the error through.

> Your business process might not care which task had an error

That's true, but you would never write upstream functions with the business process in mind. That is for the downstream functions to determine. Doing so would couple an upstream function to the business process, making it difficult (maybe even impossible) to use again when the next business process wants to reuse it, which would be complete insanity. A function is written to provide what makes the function useful for the sake of the function itself. It is up to the caller to decide what facets of that are useful for the business process being implemented downstream.

> You do not need to have new wrappers at every level.

You absolutely do need them. The language can work to hide that they are being created, but that does not preclude the necessity of them. This is most definitely a solved problem in other languages, but it remains that nobody has figured out how to take those ideas and apply them in Go. What was done in another language cannot be lifted wholesale and simply dropped into Go. The world is not quite so simple. It remains an unsolved problem in Go.

And understandably so. Who has time to work on solving the problem when there are comments on HN complaining that nobody has solved it yet to be made?

Re: Golang proposal: container/: generic collection types

#163

Earlier quoted context omitted.

Reducing syntactic noise is about legibility, not writing ease. The error boilerplate as it exists interrupts the actual logic and makes most of your code read as sad paths that might never execute. Of course if you want something less specific to errors, we already have do-notation as an excellent example to look at. And you can't say that's more complex for users than what they did with iterators.

> Reducing syntactic noise is about legibility, not writing ease. Exactly, which said proposal does nothing to improve upon. Despite the reduction in characters it looks exactly the same as the traditional way, offering no way to improve how one think about errors. That proposal was clearly thrown out there just to try and appease the "Go doesn't have error handling" crowd without any thought into what would actually…

I agree the ? proposal seems bad, which is why I didn't speak directly to it (but gave do notation as an example of a better solution that addresses the concern about special syntax for `error` specifically). Just the idea that explicit if err! = nil return err everywhere is somehow useful.

My experience with high level banking and low-level networking applications is that the default path for error handling is that you want to bubble up to central handlers the vast majority of the time. There's a reason why exceptions are a popular language feature; they're just hard to make work with fibres. With scripting you often don't want to handle errors at all. Just set -euo.

Re: Golang proposal: container/: generic collection types

#164

Earlier quoted context omitted.

> In the overwhelming majority of cases, you want to trivially send the error up the stack to be handled Maybe so, but for JVM languages, this "trivially" includes a stack trace. Go doesn't offer that for normal errors and there was no clear path to getting it. The need for contextual clues when debugging, and the community and then stdlib settling on error-wrapping as the way to do it, makes "simply return err" not…

> Go doesn't offer that for normal errors and there was no clear path to getting it. It does offer that for normal errors if you pass those errors via exceptions. Although you're right that using exceptions for error handling is pretty weird and generally avoided in Go, much to the chagrin of Java developers. The Upspin experiment, which eventually shaped the error additions that did make it into Go, showed a clear p…

Yes, I don't think stack traces are a silver bullet. They tell you the path taken through the code, but rarely do they tell you why that path was taken. I don't know what Scala stack traces look like specifically, but I've done my fair share of parsing Java stack traces, and they usually just get you in the vicinity of the problem without really telling you the cause. This was best illustrated with the standard exception that was thrown when a hostname failed to resolve, where the message did not contain the hostname. This one was solvable (and did get solved AFAIK), but in a lot of other cases, the relevant context is not available at the depth where the exception is thrown, necessitating exception-wrapping.

Re: Golang proposal: container/: generic collection types

#165

Earlier quoted context omitted.

> In the overwhelming majority of cases, you want to trivially send the error up the stack to be handled Maybe so, but for JVM languages, this "trivially" includes a stack trace. Go doesn't offer that for normal errors and there was no clear path to getting it. The need for contextual clues when debugging, and the community and then stdlib settling on error-wrapping as the way to do it, makes "simply return err" not…

That's just pointing out another layer of noise Go introduces. Here again in Scala, you can get stack traces for free with no extra syntax with value-based errors. There are already solutions for doing these things that have been used in anger for many years. In a language that takes error handling seriously, you have domain types for errors, type checking for exhaustive error handling, and you get info like a stack…

Ignoring the stack trace matter, which is explored in the sibling thread, it seems to me you're really taking issue with two of Go's language design choices that aren't specifically about errors: structural typing and the lack of proper discriminated unions.

There is nothing special about an error in Go, and there is no type hierarchy to anchor them against anyway. An error can be a struct{} carrying no value at all, or simple integer with no way to carry context, etc. Error-wrapping is the solution that has been devised to work within these constraints, but there's no obvious way to compose that with the various ideas around reducing boilerplate. For a point of comparison, Rust's anyhow crate was able to square this circle by adding a blanket trait impl for Context, but Go has no equivalent for that. (Also, Rust's ? operator is implemented using unstable components, allowing the details to evolve even while the feature itself is usable in stable Rust, something which also has no equivalent in Go).

As to the lack of discriminated unions, I agree wholeheartedly. There have been some proposals recently to get them into the language, with various levels of conservatism and cohesion with the rest of the language, but a lot of them are faltering on the "every type must have a well defined zero value" issue (yet another design decision that doesn't necessarily have anything to do with errors).

Re: Golang proposal: container/: generic collection types

#166
post #107

Earlier quoted context omitted.

From what I've seen, I don't think the core Go team was ignoring the lessons of Java or C#. Here's a sample quote from Russ Cox from 11 years ago on this site: [1] We have spoken to a few true experts in Java generics and each of them has said roughly the same thing: be very careful, it's not as easy as it looks, and you're stuck with all the mistakes you make. As a demonstration, skim through most of http://www.ange…

People always forget this other post when arguing for the home team. "They are likely the two most difficult parts of any design for parametric polymorphism. In retrospect, we were biased too much by experience with C++ without concepts and Java generics. We would have been well-served to spend more time with CLU and C++ concepts earlier." https://go.googlesource.com/proposal/+/master/design/go2draf...

FWIW, I do remember that quote.

It doesn't seem particularly damning to me, though, especially written by Russ Cox in hindsight. It's almost a truism that if you had spent less time on approaches that didn't pan out and more time on approaches that did eventually pan out, you likely would have arrived at a workable solution sooner.

For example, if the core Go team had spent more time exploring C#-like approaches (as some suggest they should have), it's possible that would have delayed the whole thing.

(btw, consider me a "long time listener, first time caller" -- I pretty much always pause to read your comments while flicking through discussions here, including I appreciate you often bring in historical context, even if I might have a different take, or even if I might have expressed your take differently ;)

Re: Golang proposal: container/: generic collection types

#167

Earlier quoted context omitted.

That's just pointing out another layer of noise Go introduces. Here again in Scala, you can get stack traces for free with no extra syntax with value-based errors. There are already solutions for doing these things that have been used in anger for many years. In a language that takes error handling seriously, you have domain types for errors, type checking for exhaustive error handling, and you get info like a stack…

Ignoring the stack trace matter, which is explored in the sibling thread, it seems to me you're really taking issue with two of Go's language design choices that aren't specifically about errors: structural typing and the lack of proper discriminated unions. There is nothing special about an error in Go, and there is no type hierarchy to anchor them against anyway. An error can be a struct{} carrying no value at all,…

As an aside, the "every type must have a zero value" is another level of insanity akin to pi must be 3: tempting to want it to be true but disastrously wrong. You're basically making known good practice (RAII/make invalid states unrepresentable) impossible, especially when you combine it with the lack of if-expressions. It also breaks parametric reasoning since now a function `() => a` exists.

I had to argue about this years ago with a coding standard pushing to define local variables at the top of functions and zero initialize them in C. Why default to null pointers or worse (invalid ints/structures) when you could make use of uninitialized variables be a compiler error instead!?

Re: Golang proposal: container/: generic collection types

#168

Earlier quoted context omitted.

Ignoring the stack trace matter, which is explored in the sibling thread, it seems to me you're really taking issue with two of Go's language design choices that aren't specifically about errors: structural typing and the lack of proper discriminated unions. There is nothing special about an error in Go, and there is no type hierarchy to anchor them against anyway. An error can be a struct{} carrying no value at all,…

As an aside, the "every type must have a zero value" is another level of insanity akin to pi must be 3: tempting to want it to be true but disastrously wrong. You're basically making known good practice (RAII/make invalid states unrepresentable) impossible, especially when you combine it with the lack of if-expressions. It also breaks parametric reasoning since now a function `() => a` exists. I had to argue about th…

Every value must have a zero value was congruent with the dynamic languages of the time, which is signifiant as Go was designed to "feel like a dynamic language" that was able to handle production scale (i.e. something that could handle Google-sized loads). There is a good possibility that Go wouldn't have even had a static type system if they had figured out how to make dynamic typing fast.

That may seem out of place in the world you live in, but Go was created in a world where Python, Ruby, and Javascript were the languages all the cool kids were using. Go was intended to be the language for C++ programmers who had to use C++ for performance reasons but wished they could use Python instead.

Insanity, perhaps, but Go was designed to solve a problem for the world it lived in. You don't get to choose your circumstances. For those living in paradise, they can use the languages designed for their world.

Re: Golang proposal: container/: generic collection types

#169

Earlier quoted context omitted.

Ignoring the stack trace matter, which is explored in the sibling thread, it seems to me you're really taking issue with two of Go's language design choices that aren't specifically about errors: structural typing and the lack of proper discriminated unions. There is nothing special about an error in Go, and there is no type hierarchy to anchor them against anyway. An error can be a struct{} carrying no value at all,…

As an aside, the "every type must have a zero value" is another level of insanity akin to pi must be 3: tempting to want it to be true but disastrously wrong. You're basically making known good practice (RAII/make invalid states unrepresentable) impossible, especially when you combine it with the lack of if-expressions. It also breaks parametric reasoning since now a function `() => a` exists. I had to argue about th…

Technically, Java has the same rule, it's just that all user-defined types are reference types, and so the zero value for them is always spelled "null". I'm not sure how Scala pretends otherwise, but I have worked with Kotlin, and non-nullable object fields and method arguments are always a bit of a lie, since the underlying JVM machinery permits, and defaults to, null. (Despite the original authors of Go being unfamiliar with Java, they do seem to have settled on a number of the same decisions.)

Personally, I'm fine with discriminated unions always allowing "nil" in Go, since IMO the most sensible way to implement them anyway is as a special case of interfaces. Not everyone feels the same way, though.

Re: Golang proposal: container/: generic collection types

#170

I find it so sad that people wasted millions of hours and lines of code rewriting over and over basic constructs that should have been on the language in the first place. How many years before we finally get sum types?

It's not like your idea is bad, is that each abstraction eats in one of the fundamental strengths of Go (simplicity and compilation times).

Go was never simple in action. The fact that you'd panic a nil map but not a nil slice is a foot gun. It's a pretty big foot gun. It's just one of the many foot guns.
Post reply on HN