Why Go Gets Exceptions Right
dave.cheney.net
dave.cheney.net
At the end of the day this solution doesn't work for "high level" code for the same reason `errno` and return codes aren't adequate - is forces the user to litter their code with explicit checks for exceptional cases. The whole point of exceptions is that they let the user write the code as if everything is peachy, to let the code bail out wherever appropriate, and to pass the buck for fixing it to someone who cares.
(I don't know anything about `panic`/`recover`.)
I do think a language got this system right -- conditions and restarts in Common Lisp solve this problem very well. I'm a bit perplexed that I haven't seen this model adopted a bit more widely in the programming world, particularly given how much of a headache exception semantics are.
Ack, and your sibling comment is pointing me to a monadic approach... I'm sure "learn about monads" is also languishing on my to-do list somewhere.
Edit: A quick search turn up this interesting LtU discussion: http://lambda-the-ultimate.org/node/1544
Thanks for your link too.
I just submitted it here: http://news.ycombinator.com/item?id=4521675
Note that I'm not arguing that termination semantics are better - I've never programmed with resumption semantics, so I can't say. But that's why it's not in C++.
Also consider what your statement implies: Stroustrup put too many features into C++ (bad), but he did not put feature X into C++ (also bad). You're faulting him for accepting too much, but at the same time, for not accepting one in particular. Although it should really be noted that post-standardization, what made it into C++ was a committee decision, not Stroustrup alone.
s/resumption/exceptions/ and you have a secret to performance in Java.
If this is the core reason for termination-only exceptions in C++, Stroustrup made a huge mistake: he presumed that the best exception handling for mature, stable code was the best exception handing for all code. But then he also threw in a lot of features that cripple the language from ever being able to create truly mature, stable code (see http://www.250bpm.com/blog:4). Now the most stable, mature libraries are written in C.
C++ exceptions are then unreachable code, and a properly optimized programmer will never use them.
I was talking more about the (Either Error) monad, which has not much to do with "fail".
(Edited first sentence.)
If you use the Either error result approach, you can conceivable put a stack trace (or something similarly identifying) in the Error data-type, so you can avoid the "Where did that NaN come from?" issue.
I think we need to see Algebraic data types in more languages, before we will see this approach used more often. (In my opinion algebraic datatypes and the pattern matching they enable rank in the same league as garbage collection in that they are a feature originally invented in and for functional languages, but useful outside as well.)
Yeah, knew about Either in general (see my top-level comment about sums vs. products), just not the monadic bits around it (MonadError and ErrorT, in particular).
> If you use the Either error result approach, you can conceivable put a stack trace (or something similarly identifying) in the Error data-type, so you can avoid the "Where did that NaN come from?" issue.
True - even just making exceptions take a Loc parameter seems like it could be a good step, but that would be basically requiring TemplateHaskell, which would ruffle feathers for sure...
> I think we need to see Algebraic data types in more languages, before we will see this approach used more often. (In my opinion algebraic datatypes and the pattern matching they enable rank in the same league as garbage collection in that they are a feature originally invented in and for functional languages, but useful outside as well.)
I agree wholeheartedly. Lack of easy-to-use algebraic datatypes is my biggest pain-point in languages that lack 'em.
Go aims to make concurrency right. That means you can depend on a global variable for propagating error information.
errno in C is ultimately because C doesn't have multiple return values, and the alternatives (pointer arguments as implicit output) are sucky. Go has multiple return values, and so returning an error is natural.
The odd constructoin (the function returning an int which is dereferenced) allows the errno macro to work as an "lvalue", i.e. that you can still say "errno = 0" to clear its value.
f := os.Create("output.txt")
Will give a compile error multiple-value os.Create() in single-value context:
and should be written as one of these: f, err := os.Create("output.txt")
f, _ := os.Create("output.txt")
where the second indicates that you are ignoring the returned error.I say "partial" because if you ignore all the return values you can implicitly ignore an error:
fmt.Println("Hello")
It is quite common to see: if err != nil {
//handle error
}
in Go, as their philosophy is that the caller should handle the error, not an unrelated piece of code. Yes, this does sometimes look like "litter", and it doesn't really make for the most impressive code. However, you won't be surprised by it, it's simple to understand, and if something goes wrong, you'll know what, when, and what was done about it. This has been Google's opinion on C++ for a while. http://google-styleguide.googlecode.com/svn/trunk/cppguide.x... lists some pros and cons. Writing code that ignores errors is one way of writing code, I think it's idiomatic Java and Python (please correct me), but that's not the way that was chosen for Go.I don't really use panic() except to indicate bugs:
if playerCount == 2{
go multiPlayer()
} else if playerCount == 1{
go singlePlayer()
} else {
panic(playerCount)
}
However they can be useful inside libraries. For example if you call fmt.Printf("%s",s), where s has a method s.String(), will print the result of s.String(). Sound good?
But what if s is a nil pointer? Well, you may wish s.String() to handle that and return "A nil s", which is fair enough. However is s.String does not handle this case it may panic(), leaving fmt.Printf to recover() and write "<nil>" instead.Or you can ignore all the return values, like in your fmt.Println() example.
> I say "partial" because functions that only return 1 value can ignore the return implicitly:
fmt.Println() returns two values, one of which is of the error type. You can't ignore just one of those values, but you can ignore both of them.
And while it would have been better to treat those errors in the first place, we do know that's not possible and trying to force the developers only leads to code such as ...
try {
...
} catch(Exception ex) {}
Checkout this piece on checked exception, also by a Google employee:http://googletesting.blogspot.ro/2009/09/checked-exceptions-...
I found this quite thought-provoking about different error handling techniques, so thought it worth submitting.
For instance, the built in map type returns a pair of values, the first is the actual matched value, the 2nd a boolean. If a key is not found the value is set to the type's zero-value, and the boolean as false. So for a map of string->integer, instead of throwing a NoSuchKey exception or something, just returns 0,false. So there's no mandatory ceremony when you don't care.
>>> a = {}
>>> a['foo']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'foo' // don't care
value = map.get(key);
do bleh
// care
if (map.containsKey(key)) {
do blah
} else {
value = map.get(key);
do bleh
}
To be clear, there are two kinds of errors:1. The code is screwed up. Java runtime exception, log and hope someone is paying attention to the logs.
2. The world is screwed up. Java checked exception, catch and return a nice message to the user.
value = map.get(key);
if (null == value) {
// Handle no value in the map
}
Any class correctly implementing Map in Java won't throw an exception if there is no value for an input key (http://docs.oracle.com/javase/6/docs/api/java/util/Map.html#...) and some don't allow you to put null values in to begin with (http://docs.oracle.com/javase/6/docs/api/java/util/Hashtable...).Now, it appears that the argument is about how to save a trivial amount of work for the CPU, when it's unclear whether this is a bottleneck, or whether the JIT can optimize away the two calls, or, in the remaining 1% of the cases, using the null idiom makes this a non-issue.
All at the cost of spending 1 of my 7 brain registers to handle "mandatory ceremony" of adding a _ to every map.get call, regardless of whether I actually know that the given map access should never see an unmapped key, which is the vast majority of cases in my day-to-day coding experience.
At the very best, the argument is about "multiple return values are useful in some cases". But tread carefully, lest every caller has to worry about useless return values.
So this guy goes on about how bad unchecked exceptions are, then waxes poetic about Go's, erm, unchecked exceptions.
To add to the weirdness, he seems to think that most people like checked exceptions, when in fact he appears to be very much in the minority.
The concurrency in Go (and to a lesser extent in Node.js--probably) is amazingly performant. For example, when I poll & parse 100 RSS feeds and the slowest one takes 6 seconds to return from the remote site, the whole routine returns in < 7 seconds. A synchronous single-core app would take up to 6 seconds * 100 = 10 minutes for the same process. In Go, when I call out to the remote sites, each call returns immediately, so that all 100 remote calls finish immediately. Go puts my routine into a dormant state while waiting for the remote sites to return data. So during that 7 seconds my app may have ~5 seconds of dormancy to do other processing (in a completely independent code path), again using all the machine's cores.
If you're in a goroutine, is your caller the place your goroutine was launched from, or whoever is hooked up at the other end of the channel that is expecting you to respond? In some sense it is both. Where would you propagate an exception? How do you choose?
Returning exception values provides an answer - it is up to you.
It is a combination of Go's technique but with panics for exceptional errors.
Like Go, functions should return error values for known errors. For instance, `file:open/2` returns `{error, enoent | eacces | eisdir | enotdir | enospc}` for known error conditions that can be handled. Unknown, exceptional conditions, like a NAS going down, can't be compensated for because the set of unknown errors is unbounded. There could literally be an infinite number of things that can go wrong that you don't know how to handle. For try/catch style error handling, the only safe way to handle the unknown errors is to do a Pokemon catch clause globally or around every single potentially error causing function call.
Because of the fact that you can't stop errors from happening is why Erlang has processes. When an unknown error occurs, the individual process dies and anyone monitoring that process is notified. OTP has supervisors whose sole job is to watch over processes and restart them if they crash.
Any state that needs to be preserved between restarts is stored somewhere safe (Erlangers calls this the "error kernel") so that when the process comes back it'll resume where it left off.
Once you structure your code to "Let it Crash", your code gets much simpler and safer. #1, the error return values forces you to compensate for known errors that can happen with a function, #2 you have to think about data durability in order to "Let it Crash". #3 Supervisors take care of the rest.
Data durability is made much easier with the fact that individual processes are single threaded and data is immutable, this makes them transactional by nature.
It correctly ignores the work on "countless other languages", because they didn't have any if at all traction to the trade, and don't matter much as far as 90% of programmers are concerned, and the few languages outside those that do (Perl, PHP, Ruby, C#, JS, Python, VB) are quite similar to the above.
result AND error
whereas it would make much more sense to return:
result OR error
After all, if the error is true the result is invalid. This is one thing exceptions basically gets right. Go, much like the errno paradigm, relies on the programmer's diligence to know when it is safe to use the result. A better designed language takes away the option of accidentally doing something wrong.
Besides exceptions, this better approach can be accomplished by returning a union type variable that knows its current type (languages outside the C lineage call this sum types or tagged unions).
var foo = Bar();
if( typeof(foo) == Error ) {
// handle error
}
else {
// result is safe to use
}Assuming you construct tagged unions like so:
Valid 123
Error "Error message"
You could pattern match on the left side of an assignment like so: Valid foo = dosomething()
use(foo)
Which would extract the wrapped value into 'foo' if the union's tag was 'Valid', or aborted/panicked/did something else somewhat sane if the value didn't match.False. A returned error does not necessarily mean the result is invalid. It could also mean something went wrong and you only have a partial result (which can be useful). Or that whatever the function returns doesn't directly have anything to do with what the function does (mostly for functions which are called for their side effects).
An example I can spontaneously come up with is the very common Writer interface. Its Write() function returns an int and an error. The int denotes the amount of bytes written to whatever is implementing the interface. This value is useful regardless of whether there was an error or not. You don't want an OR here. You want an AND.
Besides, what you are proposing could easily be done in Go. After all, errors are only things implementing the Error interface. But that doesn't really add to code clarity. Also, your code snippet isn't that different from how Go error handling looks right now. I don't see the practical difference.
if a := read() then write(a)
read() could return a false or zero value and still "succeed", calling write(a). If read() returns "failure", then write(a) will not be called. (I don't recall what the value of `a` will be in that case.)https://en.wikipedia.org/wiki/Icon_programming_language#Goal...
It's been years since I've done any MFC work, but calls like:
HWND myWin = GetTopWindowTitle(region, out title, out size, out whateverElse);
Then you'd have to either assume that myWin was not an error code and just use the out parameters, or you'd explicitly check the myWin value each time.
If my code performs several operations in a row, any of which could return an error, do I have to check the returned error value after each one?
For example if performing file or network I/O?
The upside of all that is that you can craft a piece of code to be practically flawless, since you know how the libraries you are using are supposed to break if they break.