I also spent a significant amount of time discussing with many people, including the Go team, and I am glad the proposal was declined, not because I like “if err != nil {…}” but because the proposal to add “try()” was not solving a good problem. Many, and I would say, every Go programmer wants better error handling, but “try()” was not it.
I hope the Go team keeps exploring other ideas to hopefully one day have a better error handler.
Usually to support this the language needs Enum support and a proper type system neither of which golang has. So I'm ok with the developers just baking in syntax for an error monad with specific sugar for extracting the value or handling an error.
Special purpose syntax can buy you a lot more. For example Swift’s try makes it obvious which statements contain error handling without burdening each expression.
First: The monad allows for composition of functions. Returning two values does not. It breaks the flow of a function pipeline and forces you to handle every error in the same way.
Second: Extracting the value via pattern matching guarantees that the error will either be handled or used correctly. This is a way to 100% guarantee that there are No runtime errors. That's right. Using the error monad with pattern matching makes it so that there is zero room for runtime errors.
Why create a language that has runtime errors? Create a language that forces you to handle all possible runtime errors before it even compiles.
According the criteria of safety, zero runtime errors, and expressivity via composition the monad is the Best solution. There is literally no other way of error handling that I know of that can catch runtime errors at compile time.
I know you tried to flip my statement on it's head by using the term "criteria" as if there are many many different criteria for "best." And you are right, programming is an opinionated thing. However, ZERO runtime errors is a too powerful of a feature to assign to a specific criteria. Such a feature is so powerful, it should be part of EVERY criteria.
If you don't understand completely what I mean by "composition" or how pattern matching and a maybe monad can guarantee a runtime error will NEVER occur, ask me to elucidate, I'm happy to clarify.
Presuming you know how the maybe monad (or similar named monads) handles errors. A MaybeOutOfMemoryError works in a similar way in the sense that any attempt to use the value meaningfully will force you to handle the error.
data MaybeOutOfMemoryError a = OutOfMemoryError | Some a
ioFunction :: MaybeOutOfMemoryError a -> IO
ioFunction OutOfMemoryError = println "ERROR"
ioFunction Some _ = println "No ERROR"
exhaustive pattern matching with the error flag enabled makes it so that if you forget the OutOfMemoryError case an error will occur during compile time.Any function that can potentially trigger a memory error should be forced to be typed like this. However in programming, ALL functions can potentially do this, and side effects outside of the scope of the function (total memory available) will trigger it meaning that the only way to do this effectively is to make ALL functions typed this way. Which is inconvenient to say the least.
That being said you can just type the main function as something that can only accept this monadic value forcing users to wrap this handling in a top level function:
myWrittenProgram :: MaybeOutOfMemoryError a
-- not defined in this example
-- main is forced to be typed this way just like how in haskell it is forced to be typed IO ()
main :: MaybeOutOfMemoryError a -> IO ()
main Some a = printLn a
main OutOfMemoryError = printLn "Error out of memory"
Obviously the above has some issues with the rest of haskell syntax and the way things are done, but I'm just showing you a way where it is possible to make sure that a memory runtime error does not happen (that is an unhandled runtime error).That being said unless you know haskell or know what a maybe monad is, you wouldn't be able to fully comprehend what I said. Exception monads exist in haskell, but nothing is forcing the main function to be typed this way.
That is, the verbosity would certainly benefit from some sugar, but the semantics -- errors managed by separate expressions/blocks immediately adjacent to the error-generating code -- is fundamental to the language, and one of its great strengths.
So yeah, it's actually quite fast to dig in a Golang codebase. You notice that as a normal user when you find it faster to read the standard library vs reading the doc, or when you read an implementation instead of reading a spec/algorithm to learn about it.
My guess is that gofmt is a huge factor in this, but also the fact that there's not a huge amount of built ins, anf that the standard library is pretty complete.
It really is. My code looks like your code and the next person's code. Go is opinionated and strict and that makes reading other people's code so much easier
IMHO we're about 10 years overdue for committing a canonical representation of that AST to version control instead of treating a particular serialized visualization of it as the single source of truth (and spending countless hours debating which format is "good enough" for everyone in every situation).
"The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt." – Rob Pike
First, the docs link to the actual line in source, so it’s just a click away and available in the exact context I need it.
Second, I know I’ll be able to understand it: there are usually very few dependencies, so I can usually get all the context I need from a single file; the source formatting is familiar; code style, like variable names, are familiar because of the cultural influence of the Tour of Go; and there is usually no magic anywhere—I know that things are exactly what they appear to be, like when I see a variable declaration, it is not secretly calling a complex function (this is vital to human knowledge and is aligned with Objectivist epistemology’s “law if identity”).
Third, every time I look at the standard library source, I become a better programmer. I’ve lost track of the number of times I’ve thought to myself “oh, that’s a clean way to organize this kind of code!” I often end up immediately using what I learn from the stand library source.
One of Go's major selling points (for me anyway) was a solid standard library written mostly in conventional Go style. It's a great example of how Go code should be written, and a wonderful learning tool for the language.
It also makes participating in the community easier because that's one less thing you have to learn in order to contribute.
a = append(a[:i], a[i+1:]...)
That’s the recommended implementation of erase(). After this, is the original object referred to by ‘a’ modified? How can you tell?Let’s pop from a stack:
x, a = a[len(a)-1], a[:len(a)-1]
Did you read that 100x faster than ‘a.pop()’?Now this:
a = append(a[:i], append(make([]T, j), a[i:]...)...)
This is an operation called “expand.” What does it do? It is an honest question, I have no idea.These are completely idiomatic examples taken from the Go wiki. They are not readable.
I think this is one of those mistakes in the language that exist because early-on Go could have been a true systems language but it's been adopted by a large number of devs more as an infra and high-level automation language where correctness is more important than performance.
Don't get me wrong, this isn't a general-purpose argument and I'm not one of those people who thinks that the language should completely get out of your way: eg I'm not a Rust user but its unergonomic handling of memory safety seems far preferable to the ease with which you can shoot yourself in the foot with C++. I definitely see the appeal of Go's handholding in a directional sense. But the degree to which it takes it makes it feel like an unserious or educational language, unnecessarily difficult to get actual work done in, like Javascript but for the exact opposite reasons (and to be clear, Javascript is INFINITELY worse).
This makes it sound like Im more negative on Go than I am, but I think it's the first time I've been able to articulate what deflated my initial interest in it and kept me away from it. Perhaps if feel differently if I had still been a student and new to programming when Golang was released.
And there's nothing wrong with that, that's exactly Go's target audience.
I've used Go professionally for 6 years and love it, so the patterns are ingrained in my head and don't bother me. But it seems pretty clear that it's much more arcane than alternatives.
Tell me again how easy it is to debug Go. In other languages the compiler can just tell me in _seconds_ that I done fucked up.
If you response includes "You're doing it wrong if you have deeply nested framework code" then you can rightly fuck right off too.
> If you response includes "You're doing it wrong if you have deeply nested framework code" then you can rightly fuck right off too.
I mean, can you point me to an example? Absent more context, I'm pretty confident that you're indeed doing at least something wrong...
That's a pretty common use case but ignore that for a moment and just distill the problem down to it is possible to have null pointer issues in Go code.
Another example eith sufficient use of goroutines and channels it gets really tricky to debug something when a receiving channel blocks because something isn't sending.
There's plenty more. I don't find Go any simpler to debug than Java, Kotlin, Rust, or Python and in many cases it is significantly more difficult because you're constantly fighting the type system or working through huge amounts of boilerplate.
I often get the feeling that Go users are implicitly comparing it to languages like Python or JavaScript.
I've watched a client try to debug a Go codebase they had. It was sad. Their web server just returned 500 Internal Error and they tried to track down why from logs, but by the time the error had made it back up to the serving loop most information about where it came from had been lost. I suggested they attach a debugger to a server to see what's happening, they said debuggers don't work that great in Go and so people don't use them much.
If they'd been using exceptions they'd have a stack trace and could have pinpointed the source of the fault in seconds.