5 ms·
A while ago, I had attempted to do it the Rust style with a Result type in Java (where it is called Data oriented programming) and the result is not great. I wa
by redditor98654 1y ago
A while ago, I had attempted to do it the Rust style with a Result type in Java (where it is called Data oriented programming) and the result is not great. I was fighting a losing battle in a naive attempt to replace checked exceptions but still retain compile time type safety.
https://www.reddit.com/r/java/s/AbjDEo6orq https://www.reddit.com/r/java/s/AbjDEo6orq
- Defletter 1y agoYeah, Java's generic-type erasure makes result types difficult, though as you mention in your code example, this can be mitigated some using switch guards. But you could also go into another switch: switch (result) { case Ok(var value) -> println(value); case Err(var ex) -> switch (ex) { case HttpException httpEx -> { // do something like a retry } default -> { // if not, do something else } } }
- lock1 1y agoInteresting anecdote. At $DAYJOB, I'm currently migrating a non-performance sensitive system to a functional style, which requires defining basic functional types like Option<> and Result<>. So far I think it works pretty well and certainly improves edge case handling through explicit type declaration. My current stable incarnation is something like this: sealed interface Result<T,E> { record Ok<T,E>(T value) implements Result<T,E> {} record Error<T,E>(E error) implements Result<T,E> {} } Some additional thoughts: - Using only 1 type parameter in `Result<T>` & `Ok<T>` hides the possible failure state. `Result<Integer>` contain less information in type signature than `Integer throwing() throws CustomException`, leaving you fall back on catching `Exception` and handling them manually. Which kind of defeats the typechecking value of `Result<>` - A Java developer might find it unusual to see `Ok<T,E>` and realize that the type `E` is never used in `Ok`. It's called "phantom types" in other language. While Java's generic prevents something like C++'s template specialization or due to type erasure, in this case, it helps the type system track which type is which. - I would suggest removing the type constraint `E extends Exception`, mirroring Rust's Result<> & Haskell's Either. This restriction also prohibits using a sum type `E` for an error. - In case you want to rethrow type `Result<?,E>` where `E extends CustomException`, maybe use a static function with an appropriate type constraint on `E` sealed class Result<T,E> { public static <E extends Exception> void throwIfAnyError(Result<?,E>... ); } - I like the fact that overriding a method with reference type args + functional interface args triggers an "ambiguous type method call" error if you try to use bare null. This behavior is pretty handy to ensure anti-null behavior on `Option.orElse(T)` & `Option.orElse(Supplier<T>)`. Leaving `Option.get()` as a way to "get the nullable value" and a "code smell indicator"