4 ms·
Well, they are kind of more difficult than exceptions, but I think that's the point. Handling errors by use of an exception mechanism is discouraged in idiomati
by jimjimjim 10y ago
Well, they are kind of more difficult than exceptions, but I think that's the point. Handling errors by use of an exception mechanism is discouraged in idiomatic go.
A big part of the style and simplicity comes from not needing to worry about what possible exceptions could come bubbling up from a library stack and under what conditions.
Even though people complain about the repetition of "if err !=nil" at least you know from the function declaration if an error is could occur.
- catnaroek 10y agoFWIW, I don't have anything against checking errors - I agree that it's preferable to exceptions, but the way it happens to be done in Go is rather distasteful. A sum type, as suggested by TheDong, would make things clearer, by statically forcing the alternatives to be: (0) A correct result (and no error object) (1) An error object (and no correct result) Ruling out: (0) Both a correct result and an error object (1) Neither a correct result nor an error object By “ruling out”, I mean “it's a compile error”, rather than “it's a pattern, and the community won't appreciate it if you deviate from it”. Panics as normal control flow are just as unidiomatic in Rust as they are in Go, but at least: (0) There's a decent (in fact, superior) alternative for error handling. (1) You don't risk leaking resources. RAII has that covered.
- fauigerzigerk 10y agoI like the conceptual clarity of sum types, but in some cases they may not be flexible enough (same as exceptions). Consider the case of io.Reader: https://golang.org/pkg/io/#Reader https://golang.org/pkg/io/#Reader Read always returns the number of bytes read, and if an error occurs after some bytes have been read (or at EOF) it returns that error as well. It does feel a little dirty, but it's also efficient and pragmatic.
- catnaroek 10y ago> Read always returns the number of bytes read, and if an error occurs after some bytes have been read (or at EOF) it returns that error as well. Sum types can do this as well. In OCaml syntax: type result = | Success of int (* bytes read = buffer length *) | Error of int * err_info (* bytes read, error info *) class type reader = object (* attempt to fill the buffer *) method read : buffer -> result end > It does feel a little dirty, but it's also efficient and pragmatic. The pragmatic thing to do is to make illegal states irrepresentable, so that the programmer doesn't have to worry about them. There's literally no benefit to losing conceptual clarity.
- fauigerzigerk 10y agoI understand why this may be formally preferrable, but it still suffers from the same conceptual clarity issues. What I mean by conceptual clarity is knowing for sure, without looking at the documentation of each function, that it returns either an error or data for further processing, but never both. That's not the case with your solution either. You're just packaging the same conceptual ambiguity in a slightly different way.
- catnaroek 10y ago> That's not the case with your solution either. It is. A single call to the `read` method of a `reader` object returns either `Success` or `Error`, but never both. It's handled like this: match my_reader#read my_buffer with | Success(len) -> (* success logic *) | Error(len,info) -> (* error-handling logic *) > You're just packaging the same conceptual ambiguity in a slightly different way. There's no ambiguity whatsoever. The cases are separate, and you can't use the variables of one case when handling the other. There's still literally no reason to sacrifice conceptual clarity.
- fauigerzigerk 10y agoI don't dispute the formal correctness of your solution or its theoretical desirability. But your solution cannot fix the conceptual lack of clarity that follows from the decision of the API designer to return both error information and data for further processing from the same function call. It's not a formal problem.
- catnaroek 10y ago> But your solution cannot fix the conceptual lack of clarity that follows from the decision of the API designer to return both error information and data for further processing from the same function call. Why is it unclear? If the API supports using a reader after an error happens, the types have to reflect just that. It's not the kind of API I would design, but it's what you asked for. > It's not a formal problem. The inability to express your intention in a mechanically enforceable way is very much a formal problem.
- dom96 10y agoYou can easily implement a feature which lists all possible exceptions that a specific function raises. Java has a feature called checked exceptions that ensure that you handle all possible exceptions in your program, although admittedly this feature is annoying. Another language called Nim[1], that is in fact a pretty good Go alternative, offers a similar feature[2]. But in Nim it's opt-in and the documentation includes information about each procedure and the exceptions it raises, this is shown by the `raises` pragma where `raises: []` means that no exception is raised. For example, you can easily see that `parseInt` may raise a `OverflowError` or a `ValueError` in the documentation[3] whereas `normalize` doesn't raise anything[4]. This information is inferred by the Nim compiler automatically. In my opinion this is the best of both worlds, because when your program does crash, you get a nice stack trace that is just as informative as Python's stack traces and you always know the exceptions that each procedure may raise. Here is an example stack trace from a compiler crash: Error: internal error: getUniqueType Traceback (most recent call last) nim.nim(97) nim ... msgs.nim(957) internalError msgs.nim(866) rawMessage msgs.nim(863) rawMessage msgs.nim(796) handleError 1 - http://nim-lang.org http://nim-lang.org 2 - http://nim-lang.org/docs/manual.html#effect-system-exception-tracking http://nim-lang.org/docs/manual.html#effect-system-exception... 3 - http://nim-lang.org/docs/strutils.html#parseInt,string http://nim-lang.org/docs/strutils.html#parseInt,string 4 - http://nim-lang.org/docs/strutils.html#normalize,string http://nim-lang.org/docs/strutils.html#normalize,string
- twic 10y ago> Java has a feature called checked exceptions that ensure that you handle all possible exceptions in your program, although admittedly this feature is annoying. I think it's mostly annoying because checked exceptions were used badly in the early years of Java, so there are methods which throw loads of unrelated exceptions which have to be handled separately. Better design of exception hierarchies, and the availability of multi-catch, makes this quite a bit better. Although it's recently got worse again because none of the shiny new stream stuff supports lambdas which throw checked exceptions. You have to either stick to unchecked exceptions and lose type safety, or probably better, rotate the exceptions sideways in type-space and use an Either: https://github.com/poetix/ambivalence/blob/master/src/main/java/com/codepoetics/ambivalence/Tryable.java https://github.com/poetix/ambivalence/blob/master/src/main/j...