As much as I like Go, I don't think we need to have an article about it every day, especially one that is several months old and contains wildly out of date statements like "panics are always fatal".
To be honest I don't think there's any practical difference between this and using `errno` in C. In fact, I suspect Go may have adopted a more complicated solution simply because we're squeamish about using global variables.
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.
This was my assessment as well -- it's just a cleaner way to use errno.
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.
There was a great thread in a previous discussion on HN about error handling that had a reply from lkrubner (currently, and likely forever, the top response on that thread) that went into some detail, causing more ensuing discussion. (edit: Ah, and then he links to that same article you linked to!)
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++.
That's pretty shocking, given Stroustrup's propensity to just dump everything into C++. It's also a bit of a shame it didn't get a chance to see the wider audience C++ would've brought.
People have that impression - that Stroustrup "dumped everything into C++" - but it's demonstrably not true if you read D&E. Every new feature that made it into the language after the standardization point had a working implementation, and people had experience with that implementation. He goes over lots of rejected features.
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.
Because resumption wasn't actually necessary for such a context inquiry, they removed it and found a significant speed increase in that part of the system.
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.
Yes. But that's because the default is non-monadic wrapped and an assumption that it can't go wrong. I'd had to see non-monadic wrapped values to silentry go wrong or raise an exception. (You can do something like Go's panic in pure values. But you are not supposed to do that for recoverable conditions.)
It's a way of getting context-specific (but not error-specific) behavior, and when dealing with functions producing a polymorphic monadic value it threads very nicely. That said, the approach in Haskell of fail always taking a string (which doesn't necessarily get stored/used anywhere) has a bit less to recommend it. It's also slightly confused conceptually to put "fail" in the Monad typeclass but that mostly bugs the theorists. The biggest downside, though, is that it can often make the source of errors harder to find - cf. hunting NaNs in floating point arithmetic.
Ah, yeah, wasn't as aware of those as I should have been. Still suffers from the "where did that NaN come from?" issue, but looks like good stuff. Any idea why it's not used more?
Haskell actually has three (or four) competing exception handling mechanisms nowadays.
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.)
> Haskell actually has three (or four) competing exception handling mechanisms nowadays.
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.
The big problem with errno is that it's a global: there's exactly one of them for your program. As soon as you start using threads you are screwed.
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.
errno is generally defined to be a macro like: (errno_location()), at least when you use the -pthread option with gcc.
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.
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.
This doesn't sit well with me. Who is to say what's catastrophic and what's not? Should I use all libraries in fear because passing a nil pointer or an improperly formatted date may cause it to terminate the program with no recourse? Suddenly every library is a ticking time bomb with a scorched Earth policy for things it doesn't like. How am I to know what's going to cause my valuable program to simply die in the middle of the night? Look at the outdated/incomplete documentation? Pore over the complex code (if it's even available)? Now not only do I need to litter error value checks everywhere, but I also need to preemptively sanitize all input to any library calls I make and pray that I haven't missed something. This is simply trading one problem for another.
That's a potential issue with any language, I believe. In Go you can recover() from what would otherwise be a termination. For example, I have a function that grabs and parses RSS feeds. If my program is fed junk by the remote site, I don't want to terminate, I want to ignore the error for that feed (after logging it). To do this I recover() from any error at the feed level.
For me it's very common to defer error handling. For instance I'm logging most runtime errors and later I receive a nice report over email. I then click on the links provided and I get nice call-stack traces. I then go and fix those errors.
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:
Since errno is now thread local it's not the big deal it used to be, but it still contains no data, so returning objects is superior because it can provide error context and the error information can be chained together as flows up the stack. I was horrified at go's error handling policy until I learned multiple values could be returned from functions, that gets rid of the main problem with using error returns in C.
Perhaps depending on how important it is for that piece of code to be flawless, but generally yes.
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.
the timeline C -> C++ -> Java -> Go is ridiculous at best, and this ignores work on exceptions or error handling in countless other languages that could have made it better.
Wrong, the timeline C -> C++ -> Java -> Go is more or less what the industry went through (well, assuming it's getting to Go now and it'll catch on).
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.
This approach made me think of MFC calls. Instead of having multiple return values you'd have a single return value and multiple out parameters.
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.
I think the language that gets Exceptions right is Common Lisp because of the restarts. With other languages handling exceptions is complicated. You either need to know internals of the code you are using or you simply bail out and print and error. With restarts you can simply offer ways to fix it and keep the logic where it belongs, close to the "throw".
The obvious problem with the multiple return paradigm is that Go returns:
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
}
You could implement something like lvalue pattern matching, if you wanted to.
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.
I disagree. Java tried to make you handle the error (checked exceptions) and all that happened was people were irritated by it and caught and ignored them. I think at some point you have to expect the programmer to take some initiative in dealing with errors.
>After all, if the error is true the result is invalid.
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.
I fail to see how this is any better or worse than Java checked exceptions. You've got an error, you return an alternate value, which is handled by a different part of the code. Here is a different syntax to semantically write the exact same code, it's obviously superior.
The difference is that the Go style can provide a sane fallback, which the client code can IGNORE.
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.
No library in a language with exceptions as they're normally used would do that. Exceptions are for exceptional cases. You use the method that throws only if it is a programming error for it to throw; and you do not, as a general rule, catch exceptions. See eg Dictionary.TryGetValue in .net. Your argument is against a straw man, IMHO.
If an assumption can be made that you won't put null values into the map then it can be rewritten where it doesn't need to do the check described above:
value = map.get(key);
if (null == value) {
// Handle no value in the map
}
I think one of the points of the OP's article is that in Go you don't have to code like you've done here, where a single return value means one of two things (in your example: value in the map, or special reserved value that indicates the key wasn't found in the map). Go is cleaner, even if it's just a small improvement.
The argument used to be how "Go got exceptions right".
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.
Actually I was struck the other way: this appears to be basically unchecked exceptions. Functions return errors, which you can check or not if you like. The only difference is that the "exception" doesn't unwind the stack any more than the function call.
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 big complication in Go that makes exceptions tricky is, "Who is my real caller?"
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.
Erlang's error handling is very well thought out. In fact, error handling is the main reason Erlang is shaped the way it is.
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.
Am I the only one who is saying "What the heck is Go and where did it come from?!" All of a sudden, in the past month or two I am seeing a lot of talk about Go, out of thin air. What's the rage about? Is it really going to take over the world?!
Resistance is futile! It is a slick language. It hit version 1.0 in March, which is probably why you're hearing so much about it now. Its concurrency model lets you keep all the machine's processor cores busy, without you going insane; that's worth the price of admission for me.
I'm coding a web app in it right now. Go includes a package for its own web server. That's a huge benefit, because it means the app can be state-ful instead of the usual (for Python, PHP etc.) stateless. The former means you can have all the data you want ready to go, in the most efficient memory structure, when the user arrives. Which translates to great performance. For example check out http://langalot.com and somewhat slowly type in "bark".
Yes. Node.js supports only a single processor core. I don't know much about Meteor but it doesn't look like it offers concurrency features.
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.
That's good to know, thanks. I may want to use Nginx with Go someday, in a stateful way. The built-in web server may not be ready for prime time, I've read.
I would love to see some code to illustrate this article, as someone who hasn't written anything in Go I still don't know what these mechanisms look like in practice.
There is a mailing list thread somewhere that explains that the Go designers did not understand the difference between sum types and untagged unions, and hence inappropriately rejected sum types.
The primary question to answer when choosing between using error codes or exceptions for signalling errors is this: do you want the error propagation code to be written by one or more humans, or do you want the compiler to generate invisible but consistent error propagation code?
This is a lot like how node.js handles Errors and exceptions only its "panic" call is throwing an Error either intentionally or via an uncaught exception, which will crash the process and is difficult to catch due to node.js' asynchronous nature. Apart from that, every function call returns both an error and a value, and the value is irrelevant in the presence of an Error.
Go's error handling reminds me of the Icon programming language's elegant "goal-directed execution". Icon's control structures use implicit "success/failure" instead of true/false boolean logic. For example:
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.)
82 comments
[ 5.9 ms ] story [ 162 ms ] threadhttp://golang.org/ref/spec#Handling_panics
I found this quite thought-provoking about different error handling techniques, so thought it worth submitting.
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
http://news.ycombinator.com/item?id=4468849
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.
I say "partial" because if you ignore all the return values you can implicitly ignore an error:
It is quite common to see: 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:
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.http://golang.org/src/pkg/fmt/print.go#L630
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 ...
Checkout this piece on checked exception, also by a Google employee:http://googletesting.blogspot.ro/2009/09/checked-exceptions-...
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.
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.
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.
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).
Assuming you construct tagged unions like so:
You could pattern match on the left side of an assignment like so: 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.
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.
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.
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.
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.
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.
https://en.wikipedia.org/wiki/Icon_programming_language#Goal...