Earlier quoted context omitted.
>If you don't handle the error (e.g. by forgetting), the compiler will show an error because you declared err, but didn't use it. But if I make a mistake like I mentioned previously, I could very well use err, thus not getting a compiler warning, and still also use foo, which is a whoknowswhat. It should be an Either, not two seperate return values. >Making these specifics a criteria for decency is nothing but arroga…
And in Haskell you could make the mistake of ignoring the error and writing Right x = leftOrRight y And get a runtime crash when a Left shows up, Or Just x = justOrNone y And crash on None.
Why I’m not leaving Python for Go
231–240 of 245 posts
Re: Why I’m not leaving Python for Go
#232Earlier quoted context omitted.
And in Haskell you could make the mistake of ignoring the error and writing Right x = leftOrRight y And get a runtime crash when a Left shows up, Or Just x = justOrNone y And crash on None.
As you point out, in both cases the program will crash, which for many people is preferable to the program churning along with invalid data. In Go with multiple return values, it is possible to have a code path where there's an error but still use what would've been the result of the procedure. In ML or Haskell when using sum types, that code path does not exist.
Of course it exists: don't unpack the result and ignore it or pass it along, or use it through its monadic or applicative interface (to proxy-with-transform to the caller).
Re: Why I’m not leaving Python for Go
#233Earlier quoted context omitted.
While this code is nice and concise, the problem is that you don't automatically know which call failed or what the error was.
If you can, write a more concise code with exceptions which will automatically show you which call failed and what the error was. If we include all the declarations, I claim that your solution wouldn't be shorter or more readable than my C variant I'll write, which would only declare error code values, or even return strings as error reports. There's no advantage in exceptions, except when you use them for "hardware-…
void migrate() {
try {
callSql(s1);
callSql(s2);
...
endTransaction();
}
catch(SQLException ex) {
logAndAbortTransaction(ex);
}
}
Now I have an exception to log, which means I have a stack trace which is going to tell me which statement failed rather having to play the guessing game. I may even get an explanatory message for free if the exception came with a message.For me, having exceptions over return codes has a number of advantages:
* Within a single object, you get much more information (error message and stack trace). You need to return two different values if you need both a message and a return code
* You don't want to handle the exception at the call site? no problem, just propagate it up the stack. On the other hand, if you want to try the same thing with return codes, you need a way to differentiate the return codes due an error in your migrate() function and the error codes resulting from a given callSql statement.
* Which brings us to... another benefit: enforced compile-time checking of the exception. You can't write code that is going to check for a DogException when the callee throws a CatException. But it's very easy to check for -1 when the actual code is -2. Of course you can alleviate this somewhat using static constants, but it's still a safety issue.
Obviously, exceptions are not all roses either, whenever you need a new one, you need to create a class for it. But the workflow interruption is not greater by an order of magnitude than adding a static constant somewhere to represent your new error code.
Edited for better formatting.
Re: Why I’m not leaving Python for Go
#234Earlier quoted context omitted.
What you're missing is that I'm showing how error throwing and error returning are functionally the same, when you have a standard error type, multiple valued return, and an idiom of returning errors you chose not to handle.
Sure, you can handle errors and you can explicitly pass the buck. That's no different than what we do in C. Exceptions are out-of-band (from a regular return value), and what you're talking about is entirely in-band. What you're doing, functionally, is returning a tuple. This is as old as the sun and nothing at all like exceptions. I'm not sure what you think idiom means ( Hello, my name is Inigo Montoya ), but you n…
Normal exceptions, the kind that unroll the stack (and not deep magic like call/cc) are only "out of band" as an optimization. There is no behavioral difference between "save the last place you decided to handle errors, and jump there directly unrolling the stack at once" versus "unroll the stack by a series of buck-passing in band error returns, until you get to the last place you decided to handle errors". A compiler could implement exceptions by silently transforming them into Go form. And a Go compiler could silently transform buck-passing into traditional exceptions.
Re: Why I’m not leaving Python for Go
#235Earlier quoted context omitted.
If you can, write a more concise code with exceptions which will automatically show you which call failed and what the error was. If we include all the declarations, I claim that your solution wouldn't be shorter or more readable than my C variant I'll write, which would only declare error code values, or even return strings as error reports. There's no advantage in exceptions, except when you use them for "hardware-…
That's very easy: void migrate() { try { callSql(s1); callSql(s2); ... endTransaction(); } catch(SQLException ex) { logAndAbortTransaction(ex); } } Now I have an exception to log, which means I have a stack trace which is going to tell me which statement failed rather having to play the guessing game. I may even get an explanatory message for free if the exception came with a message. For me, having exceptions over r…
Re: Why I’m not leaving Python for Go
#236Earlier quoted context omitted.
>If you don't handle the error (e.g. by forgetting), the compiler will show an error because you declared err, but didn't use it. But if I make a mistake like I mentioned previously, I could very well use err, thus not getting a compiler warning, and still also use foo, which is a whoknowswhat. It should be an Either, not two seperate return values. >Making these specifics a criteria for decency is nothing but arroga…
And in Haskell you could make the mistake of ignoring the error and writing Right x = leftOrRight y And get a runtime crash when a Left shows up, Or Just x = justOrNone y And crash on None.
Re: Why I’m not leaving Python for Go
#237Earlier quoted context omitted.
As you point out, in both cases the program will crash, which for many people is preferable to the program churning along with invalid data. In Go with multiple return values, it is possible to have a code path where there's an error but still use what would've been the result of the procedure. In ML or Haskell when using sum types, that code path does not exist.
> In ML or Haskell when using sum types, that code path does not exist. Of course it exists: don't unpack the result and ignore it or pass it along, or use it through its monadic or applicative interface (to proxy-with-transform to the caller).
Re: Why I’m not leaving Python for Go
#238As usual in these threads about Go, I really wish people would consider Haskell as a nice alternative. A lot of people write Haskell off as "academic" or "impractical", which I feel is not an entirely fair assessment. Particularly: Haskell is fast, concurrent by design (whatever that means, I'm sure Haskell is :P), typed but not cumbersome or ugly (less cumbersome and ugly than Go's types, even) and--most importantly…
"So you can actually just write code like this: do val1 I have seen this (the error monad) mentioned before as a "nice" way of handling errors, even with explicit error returns. I beg to differ - the error messages produced by such a program will be obscure, as all the context is lost - if the first call to someFunction fails, for example, there's not necessarily any indication that the error came from that call rath…
This is completely incorrect. If you use Maybe, then you get Just value or Nothing, in which case there is no indication of what error occurred. This is used when you don't want to consider what error occurred, simply that the computation failed. If you use Either, then you get an error or a value. The error obviously gives the context you are looking for.
Re: Why I’m not leaving Python for Go
#239Earlier quoted context omitted.
That's very easy: void migrate() { try { callSql(s1); callSql(s2); ... endTransaction(); } catch(SQLException ex) { logAndAbortTransaction(ex); } } Now I have an exception to log, which means I have a stack trace which is going to tell me which statement failed rather having to play the guessing game. I may even get an explanatory message for free if the exception came with a message. For me, having exceptions over r…
Doing exactly the opposite from what I asked, you omitted all the declarations of all the classes you use. Note that if I use C++ I can also use the classes with the same semantic and that also contain error messages. The stack trace you mention is a debugger feature, not something your classes do by the language definition (I speak about C++, I don't know what's in Java). If you'd include all the code needed for you…
I assume you are right about C++ exceptions (the little C++ I do doesn't use them). Modern managed languages incorporate the stack trace in the exception (including Java).
I'm not sure how you propose to have both return code and error message in C++, return a struct with an error and a message? This also does not answer my points 2 and 3 about handling errors occurring at different layers and compile-time checking.
Re: Why I’m not leaving Python for Go
#240Earlier quoted context omitted.
As you point out, in both cases the program will crash, which for many people is preferable to the program churning along with invalid data. In Go with multiple return values, it is possible to have a code path where there's an error but still use what would've been the result of the procedure. In ML or Haskell when using sum types, that code path does not exist.
> In ML or Haskell when using sum types, that code path does not exist. Of course it exists: don't unpack the result and ignore it or pass it along, or use it through its monadic or applicative interface (to proxy-with-transform to the caller).
a, _ := some_computation_that_may_fail()
b := f(a)
In Go, if there was an error in `some_computation_that_may_fail`, the call to `f` will be made with a undetermined value for `a` and we end up with an undetermined value for `b`. This is why I say that there is a path where we can use `a` even when we shouldn't.Compare with OCaml:
let Some a = some_computation_that_may_fail () in
let b = f a
First of all, the compiler will warn you that the pattern matching on line 1 is non-exhaustive and will give you an example of a failing case (here, it will simply be `None`). If the programmer ignores that warning and runs the program anyway and the computation executes with an error, the program will fail at the first line, and thus the second function `f` will never be called and `b` will not contain an undetermined value.I hope this clears up what I meant, I apologize that I wasn't clearer in the first place. Cheers!