6 ms·
> Furthermore, exceptions have this nasty codegen tradeoff In OCaml (a language with blazingly fast exceptions), the worst that can happen for each try-block i
by jallmann 11y ago
> Furthermore, exceptions have this nasty codegen tradeoff
In OCaml (a language with blazingly fast exceptions), the worst that can happen for each try-block is a register spill or memory store [1]. For a managed language, that's excellent.
The only thing I don't like about OCaml exceptions is they are unchecked -- which is strangely out-of-place, given the ML emphasis on type safety.
The resistance towards checked exceptions doesn't make sense, either: used properly, checked exceptions could be virtually indistinguishable from typechecked return values. They also enable some really elegant control-flow constructs, although that may take some discipline to use properly in a large project.
[1] http://stackoverflow.com/questions/8564025/ocaml-internals-exceptions http://stackoverflow.com/questions/8564025/ocaml-internals-e...
- pcwalton 11y ago> In OCaml (a language with blazingly fast exceptions), the worst that can happen for each try-block is a register spill or memory store [1]. For a managed language, that's excellent. OK, but that's OCaml, a language with no value types (other than 31-bit ints) or RAII. So you don't need custom cleanup code in particular, since the runtime tagging tells you everything you need. That isn't an acceptable approach for Rust, which uses unboxed types with custom cleanup code everywhere. You would need an approach like the approach Go uses to implement defer, which involves pushing and popping custom handlers onto a linked list all the time. > The resistance towards checked exceptions doesn't make sense, either: used properly, checked exceptions could be virtually indistinguishable from typechecked return values. They also enable some really elegant control-flow constructs, although that may take some discipline to use properly in a large project. We could have used checked exceptions, I guess, as they're isomorphic to ADTs, but I don't think there's much of a benefit when checked exceptions still require you to annotate signatures or catch the exception. Also, without subtyping of exceptions, the annotation burden would likely be worse. And the automatic wrapping and unwrapping of values in exception types would probably make systems programmers unhappy that extra indirections and data structures are being conjured up in ways that aren't immediately obvious. Finally, checked exceptions have this annoying property whereby the easiest thing to do to handle them is to write "try { ... } catch (Exception e) {}", and programmers often pick the easiest thing to do. Compare to Rust's ".unwrap()", which is does the sensible thing if you don't want to bubble the exceptions up to your caller, and is much less typing.
- jallmann 11y ago> that's OCaml, a language with no value types (other than 31-bit ints) or RAII Hence the "managed language". Such features would certainly be more difficult to do in Rust, and agreed that exceptions probably aren't the ideal error-handling mechanism given the goals of Rust. I guess the point was that exception tradeoffs can be mostly overcome by a well-designed implementation, given the appropriate language. (Not saying other languages have bad implementations, although the entire OCaml runtime is a work of art that's very nice in more ways than just exceptions...) > checked exceptions still require you to annotate signatures or catch the exception Could the signatures be inferred with the exception type(s)? Not catching them at some point in the call stack would result in a compile error; no reason it has to be at the caller. Probably tricky for libraries, maybe having a notion of scope for exceptions would be useful. Although I'll admit I don't know the first thing about the nuances of type inference. > the automatic wrapping and unwrapping of values in exception types I could be missing something, but how would this be worse than one or maybe two dereferences at catch-site(s) and nothing in between? Is the problem with exceptions containing large unboxed values that have to be propagated up the call stack? Since call stacks can be arbitrarily long, this seems like another good reason to limit the "scope" for exceptions somehow. > easiest thing to do to handle them is to write "try { ... } catch (Exception e) {}" True, generic pattern matching has the same problem with the 'default' case (eg, match _ -> ... ), that's often a code smell. Moreover, the notion of a syntactic try-block seems like something that a "sufficiently smart" compiler could do away with, given enough inference: just match on the "returned" exceptional values [1]. But then again, what do I know about type inference... [1] OCaml has a pretty nice feature which approximates this: https://blogs.janestreet.com/pattern-matching-and-exception-handling-unite/ https://blogs.janestreet.com/pattern-matching-and-exception-...
- barrkel 11y agoTBH, I can count on one hand the number of times I've seen an empty catch block without a justifiable comment. I think it's a straw man.
- oldmanjay 11y agoI've worked as a consultant on a ton of projects with teams of massively varying skill levels, and I too can count the empty catch blocks on one hand, provided that hand has hundreds of fingers.
- sixbrx 11y agoChecked exceptions don't work very well with generic higher order functions like map and fold, which is probably why OCaml chose unchecked exceptions. Even Java has backed away from checked exceptions in its streams api, so you see things like UncheckedIOException in the Java 8 standard library now. If instead the result of the operations are fully encoded into the return value, then there is no problem with how to type map, fold etc., because results are all handled uniformly by the accumulator (for fold) or the transformation function (with map), etc just any other sorts of values would be.
- barrkel 11y agoI don't really see the semantic distinction between checked exceptions and Either / Result style return values with monadic composition of code. There's a large difference in syntactic overhead, and there are some implications in how costs appear to be apportioned, but they are more or less isomorphic after exception-style has been converted to continuation passing style. Map, fold etc. work just fine with typed exceptions if you parameterize map, fold etc. by the exception type. The problem comes with runtime polymorphism; runtime polymorphism is a firewall to static type flow. Runtime polymorphism is also fairly popular with Java developers. (Note that e.g. a monadic map is implicitly parameterized by the "exception" type via a type argument of the return type (Either, Result, whatever you like) of the transform passed in.)
- sixbrx 11y ago> "There's a large difference in syntactic overhead" Well that's rather in line with what I meant by "don't work very well". BTW, do you know of any languages/libraries that chose to define map/fold (or similar) to be parameterized over exception types like this? That sounds interesting enough that I'd like to see examples of what it would look like in practice.
- jallmann 11y ago> Checked exceptions don't work very well with generic higher order functions Can you elaborate? The only problem I can see is if the language forces the exception to be handled by the caller. That doesn't have to be the case: the language could require that the exception be handled somewhere in the call stack, which would fix this problem.