The way Optional is/could at the time be implemented is far from performant, so they probably just didn’t want to overrely on such an abstraction. Even intellij’s default linter will call out on you if you use it as a class field for example.
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?
Exceptions are for exceptional situations, result types can still be used for very much expected ones. E.g. a primitive file read operation returning either a byte or EOF is great as a result type. Some specific IOException is better modeled as an exception which you might not be interested in handling at that level.
With all that said, I’m interested in the newer generation of languages with effect types that might bridge this gap.
> 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
…
}
}
}
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?
Exceptions are for exceptional situations, result types can still be used for very much expected ones. E.g. a primitive file read operation returning either a byte or EOF is great as a result type. Some specific IOException is better modeled as an exception which you might not be interested in handling at that level.
With all that said, I’m interested in the newer generation of languages with effect types that might bridge this gap.