Попробуйте монаду в Java 8

Есть ли встроенная поддержка монады, которая занимается обработкой исключений? Что-то похожее на Scala Try. Я спрашиваю, потому что мне не нравятся непроверенные исключения.

Ответ 1

В проекте "best-java-monads" на GitHub есть Try monad для Java 8 здесь.

Ответ 2

Есть как минимум два общедоступных (например, на Maven Central) - Javaslang и Cyclops обе имеют реализации Try, которые используют несколько иной подход.

Javaslang Try следует за Scala Попробуйте очень внимательно. Он будет улавливать все "нефатальные" исключения, брошенные во время выполнения его комбинаторов.

Cyclops Try будет только улавливать явно сконфигурированные исключения (конечно, вы можете по умолчанию также поймать все) и режим по умолчанию операции только для того, чтобы ловить во время первоначального метода популяции. Причиной этого является то, что Try ведет себя несколько похожим на Необязательный - Необязательно не инкапсулирует неожиданные значения Null (т.е. Ошибки), а просто места, где мы разумно ожидаем, что не будем иметь значения.

Вот пример Попробуйте с ресурсами из Cyclops

 Try t2 = Try.catchExceptions(FileNotFoundException.class,IOException.class)
               .init(()->PowerTuples.tuple(new BufferedReader(new FileReader("file.txt")),new FileReader("hello")))
               .tryWithResources(this::read2);

И еще один пример: "поднять" существующий метод (который может делить на ноль), чтобы поддерживать обработку ошибок.

    import static org.hamcrest.Matchers.equalTo;
    import static org.junit.Assert.*;
    import static com.aol.cyclops.lambda.api.AsAnyM.anyM;
    import lombok.val;

    val divide = Monads.liftM2(this::divide);

    AnyM<Integer> result = divide.apply(anyM(Try.of(2, ArithmeticException.class)), anyM(Try.of(0)));

    assertThat(result.<Try<Integer,ArithmeticException>>unwrapMonad().isFailure(),equalTo(true));
 private Integer divide(Integer a, Integer b){
    return a/b;
 }

Ответ 3

Вы можете делать то, что вы хотите (ab), используя CompletableFuture. Пожалуйста, не делайте этого ни в каком производственном коде.

CompletableFuture<Scanner> sc = CompletableFuture.completedFuture(
                                                      new Scanner(System.in));

CompletableFuture<Integer> divident = sc.thenApply(Scanner::nextInt);
CompletableFuture<Integer> divisor = sc.thenApply(Scanner::nextInt);

CompletableFuture<Integer> result = divident.thenCombine(divisor, (a,b) -> a/b);

result.whenComplete((val, ex) -> {
    if (ex == null) {
        System.out.printf("%s/%s = %s%n", divident.join(), divisor.join(), val);
    } else {
        System.out.println("Something went wrong");
    }
});

Ответ 4

Здесь есть реализация, которая может использоваться как модель. Дополнительную информацию можно найти здесь:

Java с вычислениями на основе Try, Failure и Success

В основном вы можете сделать что-то вроде этого:

public class Test {

  public static void main(String[] args) {

    ITransformer < String > t0 = new ITransformer < String > () {@
      Override
      public String transform(String t) {
        //return t + t;
        throw new RuntimeException("some exception 1");
      }
    };

    ITransformer < String > t1 = new ITransformer < String > () {@
      Override
      public String transform(String t) {
        return "<" + t + ">";
        //throw new RuntimeException("some exception 2");
      }
    };

    ComputationlResult < String > res = ComputationalTry.initComputation("1").bind(t0).bind(t1).getResult();

    System.out.println(res);

    if (res.isSuccess()) {
      System.out.println(res.getResult());
    } else {
      System.out.println(res.getError());
    }
  }
}

И вот код:

public class ComputationalTry < T > {

  final private ComputationlResult < T > result;

  static public < P > ComputationalTry < P > initComputation(P argument) {
    return new ComputationalTry < P > (argument);
  }

  private ComputationalTry(T param) {
    this.result = new ComputationalSuccess < T > (param);
  }

  private ComputationalTry(ComputationlResult < T > result) {
    this.result = result;
  }

  private ComputationlResult < T > applyTransformer(T t, ITransformer < T > transformer) {
    try {
      return new ComputationalSuccess < T > (transformer.transform(t));
    } catch (Exception throwable) {
      return new ComputationalFailure < T, Exception > (throwable);
    }
  }

  public ComputationalTry < T > bind(ITransformer < T > transformer) {
    if (result.isSuccess()) {
      ComputationlResult < T > resultAfterTransf = this.applyTransformer(result.getResult(), transformer);
      return new ComputationalTry < T > (resultAfterTransf);
    } else {
      return new ComputationalTry < T > (result);
    }
  }

  public ComputationlResult < T > getResult() {
    return this.result;
  }
}


public class ComputationalFailure < T, E extends Throwable > implements ComputationlResult < T > {

  public ComputationalFailure(E exception) {
    this.exception = exception;
  }

  final private E exception;

  @Override
  public T getResult() {
    return null;
  }

  @Override
  public E getError() {
    return exception;
  }

  @Override
  public boolean isSuccess() {
    return false;
  }

}


public class ComputationalSuccess < T > implements ComputationlResult < T > {

  public ComputationalSuccess(T result) {
    this.result = result;
  }

  final private T result;

  @Override
  public T getResult() {
    return result;
  }

  @Override
  public Throwable getError() {
    return null;
  }

  @Override
  public boolean isSuccess() {
    return true;
  }
}


public interface ComputationlResult < T > {

  T getResult();

  < E extends Throwable > E getError();

  boolean isSuccess();

}


public interface ITransformer < T > {

  public T transform(T t);

}


public class Test {

  public static void main(String[] args) {

    ITransformer < String > t0 = new ITransformer < String > () {@
      Override
      public String transform(String t) {
        //return t + t;
        throw new RuntimeException("some exception 1");
      }
    };

    ITransformer < String > t1 = new ITransformer < String > () {@
      Override
      public String transform(String t) {
        return "<" + t + ">";
        //throw new RuntimeException("some exception 2");
      }
    };

    ComputationlResult < String > res = ComputationalTry.initComputation("1").bind(t0).bind(t1).getResult();

    System.out.println(res);

    if (res.isSuccess()) {
      System.out.println(res.getResult());
    } else {
      System.out.println(res.getError());
    }
  }
}

Я надеюсь, что это может затенять некоторый свет.

Ответ 5

@Миша на что-то. Очевидно, что вы не сделали бы этого в реальном коде, но CompletableFuture предоставляет монады в стиле Haskell:

  • return отображается в CompletableFuture.completedFuture
  • >= отображается на thenCompose

Итак, вы можете переписать пример @Misha следующим образом:

CompletableFuture.completedFuture(new Scanner(System.in)).thenCompose(scanner ->
CompletableFuture.completedFuture(scanner.nextInt()).thenCompose(divident ->
CompletableFuture.completedFuture(scanner.nextInt()).thenCompose(divisor ->
CompletableFuture.completedFuture(divident / divisor).thenCompose(val -> {
   System.out.printf("%s/%s = %s%n", divident, divisor, val);
   return null;
}))));

который отображается на Haskell-ish:

(return (newScanner SystemIn)) >>= \scanner ->
(return (nextInt scanner)) >>= \divident ->
(return (nextInt scanner)) >>= \divisor ->
(return (divident / divisor)) >>= \val -> do
   SystemOutPrintf "%s/%s = %s%n" divident divisor val
   return Null

или с синтаксисом do

do
   scanner <- return (newScanner SystemIn)
   divident <- return (nextInt scanner)
   divisor <- return (nextInt scanner)
   val <- return (divident / divisor)
   do
       SystemOutPrintf "%s/%s = %s%n" divident divisor val
       return Null

Реализации fmap и join

Я немного увлекся. Это стандартные fmap и join, реализованные в терминах CompletableFuture:

<T, U> CompletableFuture<U> fmap(Function<T, U> f, CompletableFuture<T> m) {
   return m.thenCompose(x -> CompletableFuture.completedFuture(f.apply(x)));
}

<T> CompletableFuture<T> join(CompletableFuture<CompletableFuture<T>> n) {
   return n.thenCompose(x -> x);
}

Ответ 6

Во-первых, позвольте мне извиниться за ответ, а не за комментарий - по-видимому, мне нужно 50 репутации для комментариев...

@ncaralicea ваша реализация похожа на мою собственную, но проблема, с которой я столкнулся, заключалась в том, как примирить try... catch в bind() с законами о соответствии. В частности, return x → = f эквивалентно f x. Когда bind() ловит исключение, то f x отличается тем, что он бросает.

Кроме того, ITransformer выглядит как a → b вместо a → M b. Моя текущая версия bind(), неудовлетворительная, хотя я нахожу ее,

public <R> MException<R> bind(final Function<T, MException<R>> f) {
    Validate.notNull(f);
    if (value.isRight())
        try {
            return f.apply(value.right().get());
        } catch (final Exception ex) {
            return new MException<>(Either.<Exception, R>left(ex));
        }
    else
        return new MException<>(Either.<Exception, R>left(value.left().get()));
}

где значение равно

Either<? extends Exception,T>

Проблема с законом идентичности состоит в том, что она требует, чтобы функция f обнаруживала исключения, которые побеждают всю цель упражнения.

Я думаю, что вы действительно хотите, это Functor, а не Monad. Это функция fmap: (a- > b) → f a → f b.

Если вы пишете

@Override
public <R> MException<R> fmap(final Function<T, R> fn) {
    Validate.notNull(fn);
    if (value.isRight())
        try {
            return new MException<>(Either.<Exception, R>right(fn.apply(value.right().get())));
        } catch (final Exception ex) {
            return new MException<>(Either.<Exception, R>left(ex));
        }
    else
        return new MException<>(Either.<Exception, R>left(value.left().get()));
}

тогда вам не нужно писать явный код обработки исключений, реализовывать новые интерфейсы или беспорядок с законами Монады.