> While I do like more pure FP languages very much, I don’t think there is all that much value in everything being a value besides some mathematical elegance. Besides, a throwing exception can be turned into a value via a try-catch block, and can be just as easily tested as if it were a value, so where is the downside?
It's really cumbersome in practice. E.g. when processing results from a database query (say), you probably want to call some callback for every row and then close the transaction and return the result of that callback. But handling exceptions around that becomes a significant pain. Once you've used a language with proper result types you don't want to go back, IME.
If the exception is something you can handle at that function’s scope, just put it inside a try-catch. Otherwise, let it bubble up and let it close the transaction (or however you want to handle that). If anything, I feel it is easier to do with Java than in, say, Haskell though I will add that my experience with Haskell doing db queries is limited.
Usually what you want is to go through the rest of the rows, finish the batch and close the transaction, and then handle all the errored rows at a higher level. "Bubble up by default" works in simple cases but falls apart as soon as you're doing any remotely high-throughput work (which has to mean chunk-at-a-time processing decoupled from the orchestration), and ends up doing more harm than good, IME. And note that in Java it's outright impossible to catch exceptions from a generic expression while preserving their type - you have to copy/paste your functions for each number of possible exception types you want to be able to handle.
void transactionBoundary() {
var list = someLongListOfElems;
var resultList = List.of(); // a sum type for success and error conditions
for (var l : list) {
try {
resultList.add(process(l));
} catch (SpecificException e) {
// add to resulList or errorList or whatever to further process those instances.
// You can just use SpecificException’s fields
…
}
}
}
It's really cumbersome in practice. E.g. when processing results from a database query (say), you probably want to call some callback for every row and then close the transaction and return the result of that callback. But handling exceptions around that becomes a significant pain. Once you've used a language with proper result types you don't want to go back, IME.