3 ms·
If you squint hard enough checked exceptions are sum types through other means: try foo() catch ExceptionX ...handle X... catch ExceptionY ..
by elteto 6y ago
If you squint hard enough checked exceptions are sum types through other means:
try
foo()
catch ExceptionX
...handle X...
catch ExceptionY
...handle Y...
catch ExceptionZ
...handle Z...
compare that to (borrowing some Rust-ish syntax):
match foo() {
ExceptionX => ...handle X...
ExceptionY => ...handle Y...
ExceptionZ => ...handle Z...
}
with the added benefits that sum types can be used outside of error handling.
- TeMPOraL 6y agoYep, they are essentially sum types, but out-of-band, which is important. In particular, you can have: def quux(): xkcd = frob(foo(), zomg()) ...lots of code... def bar(): try quux() catch ExceptionX ...handle X... def baz(): try bar() catch ExceptionY ...handle Y... I assume pattern-matching can be partial without explicit default value, but (in the languages I've dealt with) it gets tricky with expressions. For instance, in C++: auto a = foo(); auto b = bar(a, quux()); return frob(xkcd(a), b); if foo(), bar() and quux() can throw exceptions, and you wanted to replace them with sum types - say, std::variant<F, Err1, Err2, Err3>, or for better semantic separation (more on this below), tl::expected<F, std::variant<Err1, Err2, Err3>> - you'll going to pray you have enough "monadic" methods in those classes (spoiler alert: you don't, as of C++17/current tl::expected) to rescue the spaghetti code you'll have to write to model this flow. Recently, I've been dealing with a C++ codebase that uses tl::expected in lieu of exceptions, and while the "main flow" becomes more readable - return doThis(x, y).and_then(do_that).map(sth).map_error(logErrors); - the side effect is that the codebase is littered with trivial functions and closures to make that interface work. It's much less readable than a bunch of catch blocks every now and then. Maybe it's a limitation of C++? I feel like a syntax for partial application would remove half of the boilerplate I have to write when chaining tl::expected-returning functions. On semantic separation, I alluded to using a "nested" sum type like (Result1 + Result2 + ...) + (Err1 + Err2 + ...) instead of Result1 + Result2 + ... + Err1 + Err2 + ... - while mathematically equivalent, they aren't syntactically equivalent in code. Grouping possible return value types separately from possible error type values improves reasoning about code - the groups tend to change independently from function to function. And if you squint, this is what exceptions give you: they separate the error sum type from return sum type, and give the former a completely different control path. This means you can think about them separately, and don't have to force-fit the success path into shape that satisfies the needs of the error path.