Yes, you MUST. That is just how Go does things. If you don't do this, then of course things are going to blow up at runtime. Your "unless" is the same as saying in a language like Java: unless you rescue the exceptions, the program crashes! Yes, clearly.
Personally, I love Go's error handling. I made a joking tweet one time that over 10% of all lines of code in Packer are "if err != nil". That is still probably true, but I don't think its a bad thing.
We also make Serf, which is powering some pretty large infrastructures out there, and we've never ONCE had a crash in production. Not once. We've had errors, but they were logged and handled. I attribute this to the fact that we were forced to handle every error.
Some people like exceptions, but I've always liked Go's way of things in this department.
func DoFoo(Foo foo) Cat {
bar, err := GetBar(foo)
if (err != nil) { return nil }
baz, err := GetBaz(bar)
if (err != nil) { return nil }
cat, err := GetCat(baz)
if (err != nil) { return nil }
return cat
}
Option types add metadata to a type, essentially combining `bar` and `err` into a single variable. Let `Maybe x` mean a function will return `Something x` or `Nothing`. For example (in hypothetical Go): func DoFoo(Foo foo) Maybe Cat {
bar := GetBar(foo)
if (bar.(type) == Nothing) { return Nothing }
baz := GetBaz(bar)
if (baz.(type) == Nothing) { return Nothing }
cat := GetCat(baz)
if (cat.(type) == Nothing) { return Nothing }
return cat
}
Since this is such a common pattern, Haskell has a bind operator (`>>=`) to take advantage of option types by passing the output as the input of the next function if it's a `Something`, or return `Nothing`. func DoFoo(Foo foo) Maybe Cat {
return GetBar foo >>= GetBaz >>= GetCat
}
If you care about why something failed, use an `Either` instead of a `Maybe`. In a language that supports algebraic data types, you could create your own sum type instead.There's a reason why hideous Java is so successful while beautiful Haskell is a punchline to jokes.
http://www.oracle.com/technetwork/articles/java/java8-option...
Using option types produces more robust code and prevents null pointer exceptions / segfaults. Go's idiom of multiple return values and error is a poor man's Either, which is an improvement on C's return code idiom[0].
[0] C's return code idiom led to the latest GnuTLS bug:
http://blog.existentialize.com/the-story-of-the-gnutls-bug.h...
The one-liner would be harder for me to unpack, in most situations, than a 3-liner. You've got 3 calls, give me space to work with them.
do
a <- GetBar foo
b <- GetBaz a
c <- GetCat b
return cI'm not trying to sell anyone, code however you like -- I prefer the situation of "simpler environment, don't write bugs" to "really complicated environment that makes it theoretically impossible to write bugs". I bet I can still write bugs.
No. Remember we're talking about using a language that is not broken by design instead of go. You have one operator (>>=) and it works on one typeclass (Monad). Do notation (do and <-) is merely syntactic sugar that desugars to using >>=.
>I prefer the situation of "simpler environment, don't write bugs" to "really complicated environment that makes it theoretically impossible to write bugs"
And since those are not the options available, people tell you that those are not the options available. Doing it right is very simple. Your options are "simple and with lots of chances to create bugs" or "just as simple but with fewer chances to create bugs".
To someone who isn't familiar with that particular idiom, perhaps it causes more cognitive load at first. Sometimes you have to learn new ideas and techniques to make the best use of your tools.
On the other hand, once you are familiar with the idiom, what you see there is a concise and easily readable representation of the main algorithm, together with a quickly recognisable, robust way of dealing with the edge cases.
Would you also object to someone using structured programming because it causes way more cognitive load than just writing goto everywhere?
let foo = match some_call_that_may_fail() {
Ok(foo) => foo,
err => return err,
};
The advantage is that the language forces you to handle errors: dropping the error on the floor is quite hard to do, whereas "foo, _ = some_call_that_may_fail()" is very easy in Go. (And if the call doesn't return any value at all except an error, you don't even have to write the "foo, _ =" bit; this is a nasty gotcha and is why external lint tools like errcheck are needed for Go.)I haven't spent any time using Rust, though, so maybe that construction will seem more natural when I get around to doing that.
Anyways, someone upthread called Go 'simplistic' which has both positive and negative connotations, and I totally agree. I'm happy with a simpler setup that solves 80% of problems really well and maybe there are some pitfalls you can fall into if you don't check your error values. As long as I'm not completely hamstrung by the simplicity (generics would be nice), I find the lack of features to be a win when you add up all the non-features that I don't have to think about it.
If you think of it as a big-O problem, you're worried about the coefficient, and Go is worried about the exponent. On a large problem, what Go is worried about solving will matter more. But if you're not on a large problem, that 10% of repetition will chafe you...
Which features precisely have led you to believe this? I can't see any indication of that anywhere. You seem to be creating a false dichotomy: "caring about abstractions and caring about scalability are opposing goals", so that you can ascribe one side of a nonexistent spectrum to go.
>But if you're not on a large problem, that 10% of repetition will chafe you...
Except that is simply not the case. The larger the problem, the more that duplicate code causes problems.
If it's something I "must" do, why doesn't the language force it to happen? Silent failure is a really bad default.
Sooner or later, you have to trust the programmer to not be an idiot. If he/she is an idiot, the language can try to help, but can't really fix it.
Static checked exceptions let the programmer know that there's something that needs to be handled. That's good. It can check that the programmer put a catch block somewhere, but it can't guarantee that the programmer did anything appropriate to handle the problem inside that block. It's better than nothing, but it's not enough. (And if you're going to ask what is enough, I have to reply: Nothing. If the programmer is determined to ignore the fact that there's an error to handle, the language can't fix things.)
That's why you have code review and code linters. If you have a linter, there's no way in hell you'll ever miss this has happened.
Take a beginner's Go code ... do they check errors ? Just like with C the answer is plain and simple ... no, they don't.
And "our language is just bad as java" is a pretty awful sales pitch.
>That is still probably true, but I don't think its a bad thing.
No it is not a bad thing. It is a terrible thing.
>I attribute this to the fact that we were forced to handle every error.
But you aren't forced, that's the problem.
>Some people like exceptions, but I've always liked Go's way of things in this department.
Some people like proper error signaling and handling instead of either of the broken choices you suggest.
[1]: http://www.sics.se/~joe/thesis/armstrong_thesis_2003.pdf
I'm toying about a new languagte where each function have IN/OUT/ERR channels.
So, is possible to do:
result | err = open('file)
This consume the ERR channel, however, if is not:
result = open('file')
it "trow". Inside the open, any raise is like a return for the ERR channel.
This combined with RAII & scope like in D: http://dlang.org/exception-safe.html
For example, Option types...
Even if this were true, it certainly doesn't lead to clearer code as you have your error handling code weaved into the mainline of the algorithm. Extracting error handling to the outskirts of the algorithm is a win, even if it costs a few extra lines of code.
Anyways, I think that weaving the error handling code into the mainline of your algorithm is in fact clearer. It's 100% clear what's happening at all times, and you don't have to speculate about where exceptions could create a hidden return. Maybe you feel differently, although I'd recommend trying it for a while with an open mind.
Go is made purposely dangerous, to force people to explicitly do the right thing. Java's enforcement of exceptions looks like the right thing on the surface, but it instead encourages people to handle exceptions in dangerous ways when they're in a hurry.
You could say the same thing about lambdas, continuations... basically any high level flow control concept. Higher level flow control concepts are a good thing in just about every case.
>Java's enforcement of exceptions looks like the right thing on the surface, but it instead encourages people to handle exceptions in dangerous ways when they're in a hurry.
This line of reasoning has never made sense to me. Forcing error handling is the right thing. Just because someone uses that information and does something boneheaded with it is not an indictment of the concept. An explicit reminder to handle an error case is loads better than non explicit. It's like blaming the static type checker for someone casting away type errors in a language like C. It's not a fault of the concept of static type checking if people fail to handle it properly.
True, but programming, especially in the large, is about juggling a whole lot of cost-benefit calculations. Continuations done right actually reduce the implicit hopping around you have to think about in a given situation. Lambdas can increase it, but even in those cases, save you a different kind of cost.
Higher level flow control concepts are a good thing in just about every case.
No, they are often a good thing, but they are not a good thing in just about every case. Like any tool used for programming, you use them when the cost-benefit works out.
Forcing error handling is the right thing. Just because someone uses that information and does something boneheaded with it is not an indictment of the concept.
Empiricism. How does it work out in practice? What is the prevalence of bugs caused by the particular boneheaded behavior? What are the effects on difficulty of debugging?
It's not a fault of the concept of static type checking if people fail to handle it properly.
Why not? We could use such logic to blame a huge number of additional aircraft accidents on pilot error. But pilots, like programmers, have limited bandwidth.
The cost to exception handling -- which I acknowledge is beautifully useful at times -- is that it entails an implicit hopping around of control flow. So if you combine that with compiler enforcement of exception handling, is that you force the programmer to pay the price of such implicit hopping, whether it is appropriate or not. Lazy programmers will cause problems in either case, but in the case without exception handling, it's often easier to figure out where you went wrong.
Think of it this way: Enforced exception handling makes the wrong thing to do dead easy, and the right thing to do anywhere from slightly harder to a lot harder. Not having exception handling makes the wrong thing to do zero effort, but suicidal, while the right thing to do is still anywhere from slightly harder to a lot harder.
An even shorter version: with Go's error handling strategy, lazy programmers tend to see the consequences sooner.
Can you offer an example (besides exception handling) where high level flow control concepts reduces readability or increases potential for bugs? I honestly can't think of any.
While I understand the empiricism argument, I'm not sure thats enough to sway me. People can be "easily" enough taught not to swallow exceptions. Also the explicitness of swallowing makes catching it during code review extremely easy. Actually I'd be surprised if lint tools don't already pick it up.
High level flow control concepts communicate intent, which then makes detecting deviations from that intent easy enough for tooling to catch. This is always a win. Without high level flow control, one has to deduce intent from more primitive units, increasing cognitive load and increasing potential for bugs.
Even if I were to accept that the tendency for abuse makes checked exceptions a worse case, I don't see how in-band/manual error code checking is superior to unchecked exceptions. Uncaught exceptions provide much more context and they're tied to the point of inception. Ignored error codes have a tendency to cause problems downstream that can be painful to trace back to their origin. (Unchecked) exceptions seem like an obvious win here.
Yes, there is a "pluggable" pattern in Smalltalk where you just let people park lambdas in instance variables to accomplish customization. Too much of this will result in complaints from fellow programmers about debuggability. Also, method_missing/doesNotUnderstand: wrappers or proxying produce the same complaints.
High level flow control concepts communicate intent
Good ones do. Bad ones hide it.
This is always a win.
Uh, no. Even this can be done badly.
Without high level flow control, one has to deduce intent from more primitive units, increasing cognitive load and increasing potential for bugs.
Sometimes true. Sometimes the cost/benefit works out in their favor.
Even if I were to accept that the tendency for abuse makes checked exceptions a worse case, I don't see how in-band/manual error code checking is superior to unchecked exceptions. Uncaught exceptions provide much more context and they're tied to the point of inception.
Keep in mind that the worst problem is actually with badly caught exceptions. What you are saying is: "Granted that too many tend to use exceptions badly, if people use exceptions intelligently, they are better." For one thing, are you granting it or not? For another thing: If your shop can manage to accomplish that, power to you! Go knock yourself out! The Go team has determined a different cost/benefit analysis in the context of their language. (It has more to do than just with the semantics of exceptions.)
>Keep in mind that the worst problem is actually with badly caught exceptions.
My point here was that if we granted that checked exceptions, i.e. forcing handling of an exception by the compiler, is a net negative, the construct of exceptions themselves (sans forced handling) is still superior to error codes for the reasons I mentioned.
We are not in disagreement. Higher level constructs become a nuisance when they aren't really necessary: In other words, when the cost/benefit doesn't work out. Pluggability and proxying are cited as a nuisance when they should not have been there in the first place, often put there by junior programmers who are excited at their discovery. You can think of exceptions as a higher level construct expected to appear everywhere. This makes them more prone to abuse than proxying, for instance.
the construct of exceptions themselves (sans forced handling) is still superior to error codes for the reasons I mentioned.
Again, this is still relative to cost/benefit in individual situations. In the case of go, there would be other costs external to the simplicity of code.
"But it's a checked exception!" Like 90% of the code I've written in Java (and it's a lot) just says 'throws IOException' up top and moves on. Or, if that's not enough, we catch some dumb ones that shouldn't even be checked (URISyntaxException anyone) and wrap them with RuntimeExceptions. Effectively I've eliminated exceptions.