I am writing a Result
type in Java, and I have found a need for it to have a method that performs an operation which may fail, and then encapulates the value or exception in a new Result object.
I had hoped this would work:
@FunctionalInterface
public interface ThrowingSupplier<R, E extends Throwable>
{
R get() throws E;
}
public class Result<E extends Throwable, V>
{
...
public static <E extends Throwable, V> Result<E, V> of(ThrowingSupplier<V, E> v)
{
try
{
return value(v.get());
}
catch(E e)
{
return error(e);
}
}
...
}
But Java cannot catch an exception defined by a type parameter.
I have also tried using instanceof
, but that also cannot be used for generics. Is there any way I can implement this method?
This is my result type before the addition of the of
method. It's intended to be similar to both Haskell's Either
and rust's Result
, while also having a meaningful bind
operation:
public class Result<E extends Throwable, V>
{
private Either<E, V> value;
private Result(Either<E, V> value)
{
this.value = value;
}
public <T> T match(Function<? super E, ? extends T> ef, Function<? super V, ? extends T> vf)
{
return value.match(ef, vf);
}
public void match(Consumer<? super E> ef, Consumer<? super V> vf)
{
value.match(ef, vf);
}
/**
* Mirror of haskell's Monadic (>>=)
*/
public <T> Result<E, T> bind(Function<? super V, Result<? extends E, ? extends T>> f)
{
return match(
(E e) -> cast(error(e)),
(V v) -> cast(f.apply(v))
);
}
/**
* Mirror of Haskell's Monadic (>>) or Applicative (*>)
*/
public <T> Result<E, T> then(Supplier<Result<? extends E, ? extends T>> f)
{
return bind((__) -> f.get());
}
/**
* Mirror of haskell's Applicative (<*)
*/
public Result<E, V> peek(Function<? super V, Result<? extends E, ?>> f)
{
return bind(v -> f.apply(v).then(() -> value(v)));
}
public <T> Result<E, T> map(Function<? super V, ? extends T> f)
{
return match(
(E e) -> error(e),
(V v) -> value(f.apply(v))
);
}
public static <E extends Throwable, V> Result<E, V> error(E e)
{
return new Result<>(Either.left(e));
}
public static <E extends Throwable, V> Result<E, V> value(V v)
{
return new Result<>(Either.right(v));
}
/**
* If the result is a value, return it.
* If it is an exception, throw it.
*
* @return the contained value
* @throws E the contained exception
*/
public V get() throws E
{
boolean has = match(
e -> false,
v -> true
);
if (has)
{
return value.fromRight(null);
}
else
{
throw value.fromLeft(null);
}
}
/**
* Upcast the Result's type parameters
*/
private static <E extends Throwable, V> Result<E, V> cast(Result<? extends E, ? extends V> r)
{
return r.match(
(E e) -> error(e),
(V v) -> value(v)
);
}
}
And the Either
type, designed to closely mirror Haskell's Either
:
/**
* A container for a disjunction of two possible types
* By convention, the Left constructor is used to hold an error value and the Right constructor is used to hold a correct value
* @param <L> The left alternative type
* @param <R> The right alternative type
*/
public abstract class Either<L, R>
{
public abstract <T> T match(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf);
public abstract void match(Consumer<? super L> lf, Consumer<? super R> rf);
public <A, B> Either<A, B> bimap(Function<? super L, ? extends A> lf, Function<? super R, ? extends B> rf)
{
return match(
(L l) -> left(lf.apply(l)),
(R r) -> right(rf.apply(r))
);
}
public L fromLeft(L left)
{
return match(
(L l) -> l,
(R r) -> left
);
}
public R fromRight(R right)
{
return match(
(L l) -> right,
(R r) -> r
);
}
public static <L, R> Either<L, R> left(L value)
{
return new Left<>(value);
}
public static <L, R> Either<L, R> right(R value)
{
return new Right<>(value);
}
private static <L, R> Either<L, R> cast(Either<? extends L, ? extends R> either)
{
return either.match(
(L l) -> left(l),
(R r) -> right(r)
);
}
static class Left<L, R> extends Either<L, R>
{
final L value;
Left(L value)
{
this.value = value;
}
@Override
public <T> T match(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf)
{
return lf.apply(value);
}
@Override
public void match(Consumer<? super L> lf, Consumer<? super R> rf)
{
lf.accept(value);
}
}
static class Right<L, R> extends Either<L, R>
{
final R value;
Right(R value)
{
this.value = value;
}
@Override
public <T> T match(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf)
{
return rf.apply(value);
}
@Override
public void match(Consumer<? super L> lf, Consumer<? super R> rf)
{
rf.accept(value);
}
}
}
The main use of this is to convert exception-throwing operations into monadic ones. This allows for (checked) exception-throwing methods to be used in streams and other functional contexts, and also allows for pattern matching and binding on the return type.
private static void writeFiles(List<String> filenames, String content)
{
filenames.stream()
.map(
(String s) -> Result.of(
() -> new FileWriter(s) //Open file for writing
).peek(
(FileWriter f) -> Result.of(
() -> f.write(content) //Write file contents
)
).peek(
(FileWriter f) -> Result.of(
() -> f.close()) //Close file
)
).forEach(
r -> r.match(
(IOException e) -> System.out.println("exception writing to file: " + e), //Log exception
(FileWriter f) -> System.out.println("successfully written to file '" + f + "'") //Log success
)
);
}
Just use the optimistic assumption that the interface fulfills the contract, as ordinary Java code will always do (enforced by the compiler). If someone bypasses this exception-checking, it’s not your responsibility to fix that:
public static <E extends Exception, V> Result<E, V> of(ThrowingSupplier<V, E> v) {
try {
return value(v.get());
}
catch(RuntimeException|Error x) {
throw x; // unchecked throwables
}
catch(Exception ex) {
@SuppressWarnings("unchecked") E e = (E)ex;
return error(e);
}
}
Note that even the Java programming language agrees that it is okay to proceed with this assumption, e.g.
public static <E extends Exception, V> Result<E, V> of(ThrowingSupplier<V, E> v) throws E {
try {
return value(v.get());
}
catch(RuntimeException|Error x) {
throw x; // unchecked throwables
}
catch(Exception ex) {
throw ex; // can only be E
}
}
is valid Java code, as under normal circumstances, the get
method can only throw E
or unchecked throwables, so it is valid to rethrow ex
here, when throws E
has been declared. We only have to circumvent a deficiency of the Java language when we want to construct a Result
parameterized with E
.
You need access to the class of the exception and then use some generics in the catch block.
One simple way is to pass the Class<E>
class to the Result.of
method:
public static <E extends Throwable, V> Result<E, V> of(
ThrowingSupplier<V, E> v,
Class<E> errorType) {
try {
return value(v.get());
} catch(Throwable e) {
if (errorType.isInstance(e)) {
return error(errorType.cast(e));
}
throw new RuntimeException(e); // rethrow as runtime?
}
}
Usage:
Result.of(() -> new FileWriter(s), IOException.class)
Class.isInstance
is the dynamic equivalent of the instanceof
static operator, while Class.cast
is the same as statically casting: (E) e
, except that we don't get a warning from the compiler.
EDIT: You need to think what to do when the catched Throwable
is not of the type of the exception you are expecting. I've wrapped it in a RuntimeException
and have rethrown it. This allows to keep using a fluent style for your monad, but is not transparent any more, as now any exception is wrapped in an unchecked exception. Maybe you could add a 3rd argument to Result.of
to handle this specific case...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With