6

Problem

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?

Definitions

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);
    }
  }
}

Example Usage

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
          )
      );

}
Zoey Hewll
  • 4,788
  • 2
  • 20
  • 33
  • 1
    Could you paste all code necessary to run your example ? I find it hard to reason with code without running it – suenda Feb 03 '18 at 17:53

3 Answers3

2

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...

fps
  • 33,623
  • 8
  • 55
  • 110
  • Given that the only checked exception type that `e` could be is the type `E`, would it be sensible to assume `e` is a kind of `RuntimeException` if it's not `E`, and simply rethrow it (maybe after a cast)? – Zoey Hewll Feb 04 '18 at 01:28
  • 1
    @ZoeyHewll No, `e` can also be an `Error`, which is a non recoverable error condition, such as `OutOfMemoryError`. – fps Feb 04 '18 at 01:31
  • 1
    @ZoeyHewll But catching `Exception` instead of `Throwable` would help here. Catching `Throwable` is not a good practice anyway. W.r.t. assuming that if it's not the checked exception you expect, then it's a `RuntimeException`, I think that it would be too optimistic. There are methods that throw more than one checked exception, i.e. `Class.newInstance` – fps Feb 04 '18 at 01:37
  • 1
    The type of `ThrowingSupplier` prevents it from throwing any checked exception other than the one specified – Zoey Hewll Feb 04 '18 at 01:38
  • 1
    @ZoeyHewll I think you are correct, I was thinking about a hierarchy of checked exceptions, but the signature of the closure would take care of it. – fps Feb 04 '18 at 01:43
  • Is there any way to do this without having the class as a method parameter? It feels like unnecessary redundancy with the type parameter – Zoey Hewll Feb 04 '18 at 02:39
  • 1
    @ZoeyHewll Without explicitly passing the class of the exception as an argument, I know of another way, but it's more complex. You need an abstract superclass with the generic type parameters, and in the immediate subclass, you could get the `Class` of a generic type parameter `E`. See [this answer](https://stackoverflow.com/a/18708282/1876620) for how to do it, but you'd need to make your `Result` class extend some abstract class that declares the generic type params. Or maybe create a dedicated subclass of `Result` with the sole purpose of extracting the `Class`... – fps Feb 04 '18 at 02:59
2

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Ooh, that would work! Though, we don't need to catch `Error`, as it wouldn't be caught by the `Exception` catch further down. – Zoey Hewll Feb 12 '18 at 00:42
  • 1
    @ZoeyHewll indeed, it would work without it, but I think it’s clearer when handling all unchecked throwables explicitly. It has the advantage that it continues to work, if you ever decide to change `` to ``. – Holger Feb 12 '18 at 07:58
  • I just realised a single case where this would fail, is if `E` is itself a `RuntimeException`. Though this class wouldn't really be useful in that context anyway, there seems to be no way to impose that restriction (and I'm not sure I'd want to). – Zoey Hewll Feb 12 '18 at 08:25
  • 1
    I suppose, that’s the reason why types like `Future` do not even try to declare a type variable for the exceptional case; the throwable type hierarchy is not the best design, to say it politely… – Holger Feb 12 '18 at 08:35
1

Update: this seems not to work at all. I'm keeping it here for now because I've linked to is elsewhere, and because it uses a method provided in other accepted answers, which I would like to continue to investigate.


Using Federico's answer and the answer linked in the comment, I have deduced a solution with the same method signature as the original problem, and I have created a class which encapsulates this functionality for future use.

The Result implementation:

public class Result<E extends Exception, V>
{
  ...
  public static <E extends Exception, V> Result<E, V> of(ThrowingSupplier<V, E> v)
  {
    try
    {
      return value(v.get());
    }
    catch(Exception e)
    {
      Class<E> errType = Reflector.getType();
      if (errType.isInstance(e))
      {
        return error(errType.cast(e));
      }
      else
      {
        throw (RuntimeException) e;
      }
    }
  }
  ...
}

And the Reflector:

import java.lang.reflect.ParameterizedType;

/**
 * This class only exists to provide a generic superclass to {@link Reflector}
 * @param <E> The type for the subclass to inspect
 */
abstract class Reflected<E>
{ }

/**
 * This class provides the ability to obtain information about its generic type parameter.
 * @param <E> The type to inspect
 * @author
 */
@Deprecated
public class Reflector<E> extends Reflected<E>
{
  /**
   * Returns the class corresponding to the type {@code <E>}.
   * @param <E> The type to inspect
   * @return The class corresponding to the type {@code <E>}
   */
  public static <E> Class<E> getType()
  {
    return new Reflector<E>().getParameterType();
  }

  private Reflector() {}
  private Class<E> getParameterType()
  {
    final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
    return (Class<E>) type.getActualTypeArguments()[0];
  }
}
Zoey Hewll
  • 4,788
  • 2
  • 20
  • 33
  • Hmmm, it seems that the elements of `ParameterizedType::getActualTypeArguments` are not always able to be cast to a `Class`. On a different machine, I am receiving a `sun.reflect.generics.reflectiveObjects.TypeVariableImpl`, which is a `Type` but cannot cast to `Class`. – Zoey Hewll Feb 05 '18 at 06:59