>I think the reason why people are so upset/surprised is because they expect better from the Go team. Sometimes it really feels like they take the lazy route without putting enough thought and polish into things.
I think that was the case with Go from the start. Can you think of some aspect that was especially well thought-out?
It's a crapshoot what will be an interface and what will be a struct, which is one of the fundamental features of the language.
Returned error values are sometimes errors.New (e.g. io.EOF) and sometimes random fmt.Errorf strings that couldn't possibly be handled (e.g. all of tls.go basically), sometimes actual structs (e.g. PathError), and sometimes panics.
If you can't even get error handling right and consistent, what claim do you have to consistency? At least in java it's pretty much all exceptions.
The math library, as mentioned above, is pretty darn iffy.
The existence of both 'path' and 'filepath' is possibly a mistake.
The 'heap' and 'list' in collections both seem like they should be similar, but 'heap' operates on a thing that fulfills its interface and list operates directly on arbitrary objects without some interface needed.
Due to the lack of 'protected', the std library is littered with exported fields you shouldn't use (e.g. gob.CommonType and template.Template.*parse.Tree and so on).
Sure, the library is consistent in many places because the language is so small there's little else it can do but be consistent, but the fact that it falls flat in error handling and has visible warts makes me feel that it's a very broad std library, but far less consistent than, say, Rust's (where everything returns consistent Optional error types) or Java's or Smalltalks. Sure, it's more consistent than the clusterfuck that is ruby and/or python or javascript or (heaven forbid) c/c++, but by no means is being better than any of those an achievement in this category.
> Returned error values are sometimes errors.New (e.g. io.EOF) and sometimes random fmt.Errorf strings that couldn't possibly be handled (e.g. all of tls.go basically), sometimes actual structs (e.g. PathError),
This is the advantage of having errors be interfaces rather than concrete values. Once you learn how to use it, it's a strength. The usage of structs that satisfy the interface (as in PathError) is clearly documented and completely predictable. If your concern is that a certain package could use a struct satisfying the interface and does not, well, one advantage of errors as interfaces is that you could propose this change without breaking the backwards compatibility guarantee.
It's really important to keep in mind that errors are adopted from error values in C, which means that their idioms and usage is inspired by that rather than by exception handling. Many new Go programmers (including me, when I started) were more familiar with exception handling in higher-level languages than error handling in idiomatic C, so it does take some getting used to.
> and sometimes panics.
Do you have any examples of the standard library using panics to signal handle-able errors (not counting cases in which the panic is recovered at the top-level in the library and never visible to the caller)?
> The math library, as mentioned above, is pretty darn iffy.
The only complaint I have had about package "math" is that all operations are defined on float64s, which can be annoying, but is far better than any alternative. That's an annoyance, but it's certainly not an inconsistency in the library.
Do you have any other examples of inconsistencies in the math package?
Yeah, but with C's old-school int values for errors, you can always make an equality comparison. The error interface in Go doesn't specify equality, so you can do "if err == io.EOF" in some cases and you're up a creek in other cases. Sure you can do if err.Error() == "String I'm expecting", but as the parent said, fmt.Errorf can easily make that impossible.
No, the advantage of having an interface is everything can return an error I can typeswitch on. Unfortunately, you can't (in a backwards compatible way) change io.EOF or any of the other 'constant' errors because there's so much code doing 'if err != io.EOF' which now breaks. In addition, it's backwards incompatible due to anyone doing 'reflect.TypeOf' which I guess you could argue is fine to break.
Speaking of reflection, there's a metric TON of runtime panics in the reflect package. Hell, I'm not sure there's a method in there that can't panic.
No doubt, however, you'll say that's cheating and the expected behavior of that package, so how about NewTimer (playground https://play.golang.org/p/jDKniK3aqa ). It does not give me an opportunity to recover from bad input by passing an error out, it just panics. It is also not documented that it panics afaik and other functions in time (time.Sleep) take negative durations and handle them just fine. This is definitely inconsistent with other std library functions that can take bad input and return errors.
I also think it's disingenuous to say "Keep in mind errors are from C and at least they're better than C"... the comment I'm responding to is a comment regarding go std library being one of the most consistent (implicit compared to ANY LANGUAGE) so I may bring in anything I wish, and sure Go's errors are a step up from C, but they're awful compared to other languages. They're basically passing around strings with no builtin way to create a trace of errors to return up the stack to maybe be printed, no default way to create an optional type with an error value, and no consistent way to determine which of N errors it was that was returned from a function without using a regexp 90% of the time because that damned fmt.Errorf / errors.New... And yes, using a Regexp in your error handling IS a problem.
> defined on float64, which can be annoying, but is far better than any alternative
Funnily enough, the 'rand' library implements everything by just having multiple functions and postfixing them with types (rand.Uint32, rand.Int, rand.Float32) and strconv does something vaguely similar.
Whether one or the other is better, that's another inconsistency in the stdlibrary; do you have many functions per type or only one function for one type? I have no clue how Go could have abused you such that you are able to conceive of not having a builtin int64 compatible 'min' function as a good thing.
Actually, perhaps your post is simply a plea for help as Go has tied you up and holds a gun to your head, in which case I suppose we should mount a rescue mission forthwith! Just let me write a quick timer to keep track of our progress... -1 you s.. err panic
I don't know about consistency within the standard library, but NewTimer's behavior definitely sounds consistent with the concept of distinguishing programming errors from exceptional conditions. Passing a negative number as a duration could either be defined behavior (equivalent to zero for programmer convenience and consistency with the principle that timers can fire late) or a programming error (should've checked that whatever you were subtracting to get that duration isn't backwards), but it's not an exceptional condition that can't be predicted in advance. Indeed, checking for a negative number, which is rarely necessary, is no more code than the 'if err' thing, and if it isn't necessary in a particular use of the function, you have the luxury of knowing it can't fail, and you don't need to figure out what will happen if it does.
(I bet you'd be unhappy at Rust irrecoverably panicking the thread for simply performing addition, if the result overflows!)
> It's really important to keep in mind that errors are adopted from error values in C, which means that their idioms and usage is inspired by that rather than by exception handling. Many new Go programmers (including me, when I started) were more familiar with exception handling in higher-level languages than error handling in idiomatic C, so it does take some getting used to.
It's also important to keep in mind that people who don't like Go's approach to error handling might not be in favour of the exception-handling way, either. :)
I think this is a classic case of Go's need for "simplicity" hamstringing the language. When handling min/max, they had a few options:
1) Treat numbers as a special case and have a polymorphic min/max that operates on any number type. This is out of the question because it is obtuse and irregular and neither of those things are the "Go way"
2) Properly abstract over numbers somehow. This can be weakly done using Go interfaces but 1) it would require >, < etc to all be methods on the number types. But then Go would need operator overloading and that's not the "Go way" 2) using an interface is actually bad anyways because it doesn't make guarantees that input type =:= output type. To do that you need proper generics and then use a common subtype or take it further and use a Numeric typeclass. This is way too complicated and not the "Go way"
3) Write a min/max for every number type. Due to lack of overloading, each would be named accordingly (minInt64 etc). This is ugly and definitely not the "Go way"
4) Just use the most "general" number type and write min/max for that. You can just cast on the way in and out so this "works". It doesn't require API bloat or more language features, so it's the "Go way"
> 3) Write a min/max for every number type. Due to lack of overloading, each would be named accordingly (minInt64 etc). This is ugly and definitely not the "Go way"
Have a package for each type, so you can import the one you need (e.g something like math/uint64), and eventually can rename it on import as is best fitting. Also, make it an external package in golang.org/x (like godoc&al) so as not to be bound to promises regarding language stability while at the same time offering a centralized implementation.
> 4) Just use the most "general" number type and write min/max for that
This is precisely what strconv.ParseInt does[0], with some obscure, runtime evaluated integer magic values of all things (instead of readable, type-safe, compile-time asserted enums).
Sorry, but I can't find what you're referencing with the "as mentioned above" on math; could you clarify?
My biggest problem with the math library was, IIRC, everything only takes float64s, which makes dealing with any of the other number types more difficult than it should be (I shouldn't need to cast an int to a float and back in order to get the max of two numbers).
It's quite good, but e.g. Java SDK has stuff that runs circles around Go's standard library regarding breadth and maturity, especially stuff added since 1.4 (nio, etc).
And some aspects of the Go SKD are horrible in practical use. Case in point, most things math related.
Well, yes. But Java's standard library is also huge and full of deprecated stuff. I have not done a lot of Java programming, but when I did play around with Java, I found myself spending most of the time actually browsing through the standard library's documentation (which is, to be fair, really, really good) looking for stuff.
Also, Java has a head start of nearly 15 years on Go, and the Java community is (or used to be, at least) pretty huge.
Add an extra 30 years to that. Java's designers didn't history of computer science and language development for the of sake "simplicity". (Generics, error handling, etc.)
Compared with? My reference point is python,java,clojure,c,c++. Python is huge and useful but messy and inconsistent. Java is huge and doesn't compose well. Clojure is underdocmented and hardly batteries included. C and C++ can't do anything useful without a bunch of extra libraries. From what I can see, C# is like java in this regard.
Go is a small incredibly useful and composable set of libraries that handles a vast amount of cases with a small amount of code.
I had the same feeling about Clojure, but it mostly has to do with non-standard libs. It's forced me to rely on reading source code a lot more than I did in other languages, so I'm not entirely sure that's a mark against it. I'm curious why you think that about Python, though. Care to illustrate?
> Can you think of some aspect that was especially well thought-out?
Go routines, channels and select. With very little else, it's possible to write some very useful code in a way that's concise, elegant and easy to reason about.
Only compared to something like C or explicit old-skool Java like threading. A lot of modern languages have CSP built-in or as a lib (Java, Scala, Haskell, Clojure, C++, Ada, Erlang, heck even Rust).
You seem very focused on what you can't do with it rather than focusing on what you can do with it. I've found, in practice, that you learn to adapt to not having that level of expressiveness and start thinking through problems the Go way. And I've come around to the viewpoint that expressiveness is also a liability of a language in addition to being a feature. Sharing code in a team becomes so much easier when using Go compared to other, more expressive, languages. And I've even come around to liking it for my personal projects too after returning to code I hadn't seen in months and immediately grokking it.
Maybe it's just my advancing age (for a programmer), but I find myself gravitating towards the "It's not finished when there's nothing left to add, but when there's nothing left to remove" mentality when it comes to programming languages. I find that I'm only willing to tolerate new language features when they introduce benefits that go beyond expressiveness.
> Maybe it's just my advancing age (for a programmer), but I find myself gravitating towards the "It's not finished when there's nothing left to add, but when there's nothing left to remove" mentality when it comes to programming languages. I find that I'm only willing to tolerate new language features when they introduce benefits that go beyond expressiveness.
Yes, "maybe it's just my experience and wisdom with regards to programming". Do you really expect anyone to fall for that crap?
It looks like from the comments on your gist, there are ways to do it. Whether it's expressive enough is subjective but it's worth pointing out that there _are_ solutions.
I think that was the case with Go from the start. Can you think of some aspect that was especially well thought-out?