6 ms·
It sounds like you enjoy Go. That is great. I am referring to TFA/post which sounds similar to many experiences with Go. Of course all languages have pros and
by mcculley 5y ago
It sounds like you enjoy Go. That is great. I am referring to TFA/post which sounds similar to many experiences with Go.
Of course all languages have pros and cons. I am referring to the specific situation where people are expecting a modern language and ecosystem heavily influenced by its user community. Go is not such a language. People should have different expectations.
> The history of language design is the history of people making decisions about how to design their language, and then discovering that the decision had unintended consequences, like, five or ten years later.
I agree completely, which is why it is so surprising that people adopted Go when it seemed to learn very little from history.
- klodolph 5y ago> I am referring to TFA/post which sounds similar to many experiences with Go. Lots of people have bad experiences with Java, C++, Rust, Python, or JavaScript. I've had bad experiences with all of those languages, as well as bad experiences with Go. I don't think you've managed to articulate what makes Go different. I'm not saying you should use Go. I will say this: it sounds like you have opinions about Go, and I can't tell if you're trying to imply that those opinions are somehow universal or common, or reflect some actual design flaw in Go that you can articulate. "Many are not happy" about Go generics but that's inevitable, IMO. I don't think there's any possible language design decision that won't make many people unhappy. You might as well be asking for pizza toppings that make everyone happy. > I agree completely, which is why it is so surprising that people adopted Go when it seemed to learn very little from history. I can understand why you would think that way, but Go seems to me completely different. When I look at Go, I see a ton of small decisions where it seems like (to me) that the Go language designers have taken some fairly sophisticated lessons from history. Things like: - Error handling, - Strings, - Default garbage collector tuning, - Generics, - Dependencies, - Versioning. Just to pick an example... Java's checked exceptions are a good idea, but have some unfortunate consequences when Java is used for larger projects. One of the key problems is that when you handle an error, you don't just need to know what the error is, but where the error came from. Some errors can be handled at a distance and some errors must be handled near their origin. Errors in this second category can be made into checked exceptions in Java--at least, that's the idea--but in practice, it's a bit of a mess. If you pull back from dividing errors into checked / unchecked variants, then you lose the ability for the language to help you distinguish between errors that can be handled locally and errors that can be handled at a distance. IMO, Go's decision to use error returns makes the decision that "local error handling is default", which is a pretty nuanced take on things that I really appreciate. I don't think anyone would have come up with Go's error handling approach without the lessons from Java's error handling approach. Some people hate it. That doesn't mean that it "didn't learn from history".
- samhw 5y agoThis is an extreme intellectualisation of Go's error handling, which is literally just "errors are nullable strings returned by functions, and you have to check for them"[0]. I can't see where Go has learned from anything in PL research or practice, and indeed it's famous for not having learned: errors can't be matched; any hacky matching solution is unhelped by the type system; nor does the type system enforce checking for an error before accessing a value; adding context to errors is an absolute clusterfuck (in the absence of anything that could be called a type system) and therefore you end up with a load of "failed: op failed: encountered error: service could not load foo: error: database error: EAGAIN". I just can't make sense of your argument. You say Go has learned some lesson from Java's checked exceptions, because ... errors are return values. What does that have to do with checked exceptions? Do you mean to contrast it with exceptions themselves? It certainly seems a complete non sequitur in relation to the (type) checking of exceptions. If your point is the "exceptions vs values" argument, then that's certainly one that's been hashed out at length, but I fail to see how picking one approach - of the two only approaches that any language can pick, both of which countless languages have used - is evidence of some kind of deep thought and originality.
- klodolph 5y ago> literally just "errors are nullable strings returned by functions, and you have to check for them" That’s definitely not how errors work in Go, either in theory or practice. In Go, I would characterize errors as an open union type. That’s how I think about them. They turn into strings when you log them. There is the type of error you get from errors.New. This is not the same as a string, because it is compared using pointer equality, not string equality. For example, n, err := f.Read(buf) if err == io.EOF { // ... } This is not a string comparison. > …errors can't be matched… You use ==, type assertion, typeswitch, or errors.Is/As/Unwrap is how you match errors in Go. Occasionally you will see a helper function (e.g. os.IsNotExist) but I think these functions are only there because they predate the errors.Is/As/Unwrap interface. > nor does the type system enforce checking for an error before accessing a value This sounds like some kind of dogmatic analysis not founded in PL theory. PL theory does not say “it is better for the type system to enforce checking”, because PL theory is not prescriptive. > adding context to errors is an absolute clusterfuck […] "failed: op failed: encountered error: service could not load foo: error: database error: EAGAIN". The nice thing about that is that you can then just do this: if errors.Is(err, syscall.EAGAIN) { // handle EAGAIN } Admittedly, producing good errors requires the programmer to care, and most programmers simply don’t (an observation). The error you wrote is not what errors in the applications I work with look like. I wrap errors with context when the context is relevant, unwrap context from errors if the context is implicitly understood. Equally, you will find that programmers don’t care in other languages, like Java, Haskell, or Ruby. When a programmer doesn’t care about errors in Java, you get unreadable stack traces, devoid of context. When a programmer doesn’t care about errors in Haskell, you get partial functions, which throw exceptions (which must be caught in the IO monad) even though the functions are declared to be pure. Go’s outcomes here seem pretty good to me. > You say Go has learned some lesson from Java's checked exceptions, because ... errors are return values. What does that have to do with checked exceptions? The default location to handle exceptions is at a distance. The default location to handle error return values is where they are returned. Checked exceptions are an attempt to bring exception handling closer to the location of the error, but they had ergonomic problems. The ergonomic problems with Java checked exceptions have been discussed elsewhere. > …I fail to see how picking one approach - of the two only approaches that any language can pick, both of which countless languages have used - is evidence of some kind of deep thought and originality. “Originality” is what you want to see in research languages like Haskell. That’s the frontier where new ideas are tested. However, speaking as a longtime Haskell programmer, it can often be damn hard to get work done in Haskell. Same thing with Rust—lots of good ideas, can be hard to get work done. Go is a more conservative approach. “Conservative” does not mean “better” and nor does it mean “worse”, it’s just another niche for programming languages.