5 ms·
Because exceptions can create much more readable and writable code, and DO THE EXACT SAME THING. func magic() (int, error) { a, err := foo() if err !
by jonathankoren 10y ago
Because exceptions can create much more readable and writable code, and DO THE EXACT SAME THING.
func magic() (int, error) {
a, err := foo()
if err != nil {
return 0, err
}
b, err := bar()
if err != nil {
return 0, err
}
c, err := baz()
if err != nil {
return 0, err
}
return a + b + c, nil
}
is less readable, more pedantic, and just generally more soul crushing than:
func magic() int throws SomeException {
return foo() + bar() + baz()
}
The explicit error check after every call actually doesn't provide anything useful. If any of foo(), bar(), or baz() fail, magic() is screwed, so why bother writing the same code three times? This shows that the proper place for the error handling isn't in magic(), it's in whoever is calling magic(), and yet, here' I am writing the same do nothing again, again and again in this function, and in all honesty, every function.
It's the multivalue return semantics that's the problem here. It's impossible to chain multivalue functions together to write anything succinctly. Worse yet, it's easy to simply ignore the error and create latent bugs because the first return value (which you're probably not ignoring) now has a "zero-value" which can be semantically valid. Whereas if I simply didn't catch the exception, it will be caught, perhaps by main(), but it will be caught, and I won't be populating my data structures with gibberish, which I can very easily do with Go.
- wahern 10y ago"It's impossible to chain multivalue functions together to write anything succinctly" Lua's standard error return idiom is: nil/false, error-string, error-code. The Lua standard library also includes a function, assert, which will throw an error if the first argument is nil/false. The value thrown is the second return value, which in the idiomatic case is the error string. If the first argument is not nil/false, then assert returns the entire list of values. Thus _you_ can choose the best approach based on _your_ needs. The following are _both_ examples of idiomatic Lua. Example 1: local v, errdesc, errcode = foo() if not v then if errcode == 1 then ... elseif errcode == 2 then ... else error(errdesc) end end Example 2: local v = assert(foo()) Example 2 (easy composition): local v = assert(foo(assert(bar()))) You're not limited to that. Neither assert nor error are specialized built-ins. You could throw a structured object with, optionally, a __tostring metamethod for string coercion; or you could write your own routine that manipulated the values differently. But in idiomatic Lua it's not common for thrown errors to be caught and differentiated, and code usually expects the thrown value to be a string or coercible to a string. If it's thrown it's usually because there's nothing particular that can be done, and so usually it's caught at the boundary of some logical transaction. But Lua allows you put that boundary anywhere, and you're not limited to that idiom. In some cases you want more structured exceptions, and you can have them. But as a module author the expectation is that you preserve the application's freedom of choice, so modules usually return the idiomatic tuple whether or not it's recoverable. There are some errors that Lua will always throw, like memory allocation failures. These can be caught like any error, and the Lua VM is never put in an inconsistent state. But they're an example of the kind of non-specific error where you normally only catch them, if at all, at some logical transactional boundary--an image transformation in a photo editor, or a client request for a web server. Lua is, notably, one of the few scripting languages where allocation failure is recoverable. And rather trivially recoverable, in fact. EDIT: error() is a specialized built-in: it's how you "throw" in Lua. But it will throw any value, not just a string.
- 010a 10y agoOne of the reasons why Go is so unwieldy is because it is designed to be simple. Its very easy to mistake simplicity for elegance; the reality is that simplicity can lead to elegance, but it doesn't have to. Go is an interesting language when you have a project with dozens of developers, because it explicitly, by design, does not let you introduce any concept of "your needs". Go is not ashamed of the fact that it has one way of doing things, and that way is rarely elegant. But it _is_ effective.
- wahern 10y agoI agree with that assessment. It's impossible to disagree because the designers have been very explicit about their motivations. My argument was only that multivalue error returns are not necessarily so limiting. Rather, IME they're a great compromise that can permit the best of both worlds. I always wondered why Lua's idiom was never picked up by Go. So much of Go seems lifted wholesale from Lua, or at least a shared ancestor. Particularly Go's goroutines, lexical closures, and how cleanly they interoperate; both of those constructs are much more limited in languages other than Go or Lua because of shortcuts taken to simplify the [pre-existing, broken] implementations (e.g. JavaScript and Python). But especially Go's exception mechanism, with syntax and semantics likewise almost identical to Lua's.
- nhumrich 10y agoYes yes and yes. Thank you for these perfect examples.
- richard_todd 10y agoWith some effort, it can be made less horrible. I have a utility function that lets me write it like this, whenever I can get away with doing steps after an error has occurred: a, err1 := foo() b, err2 := bar() c, err3 := baz() return a+b+c, errs.Combine(err1,err2,err3)
- jonathankoren 10y agoYeah that kinda works, except you have to make sure that you add your error code to the end every time you add something. Also, you're still stuck with doing the check if your code doesn't quit hit this pattern.