логика с использованием обработки исключений в функциональном стиле с Java и Vavr - PullRequest
0 голосов
/ 21 октября 2019

Я пытаюсь разобраться в основах функционального программирования с использованием Java 8, и у меня есть простая задача - установить свойство объекта, а затем сохранить его. Правильный тип базы данных - ltree, поэтому может произойти сбой, если он содержит недопустимые символы. Я хочу обрабатывать элементы по одному и регистрировать исключения / успехи.

Я решил использовать библиотеку Vavr , потому что Try.of() обработка исключений, и я хочу научиться просто использовать еекак это кажется очень полезным.

вот что я придумала, но я недостаточно удовлетворена:

public class PathHandler {

    private final DocVersionDAO dao;

    public void processWithHandling() {
        Try.of(this::process)
                .recover(x -> Match(x).of(
                        Case($(instanceOf(Exception.class)), this::logException)
                ));
    }

    private Stream<Try<DocVersion>> logException(Exception e) {
        //log exception now but what to return? also I would like to have DocVersion here too..
        return null;
    }

    public Stream<Try<DocVersion>> process() {
        return dao.getAllForPathProcessing()  //returns Stream<DocVersion>
                .map(this::justSetIt)
                .map(this::save);
    }

    public DocVersion justSetIt(DocVersion v) {
        String path = Optional.ofNullable(v.getMetadata().getAdditionals().get(Vedantas.PATH))
                .orElse(null);

        log.info(String.format("document of uuid %s has matadata path %s; setting it", v.getDocument2().getUUID(), path));

        v.getDocument2().setPath(path);

        return v;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Try<DocVersion> save(DocVersion v) {
        return Try.of(() -> dao.save(v));
    }
}

цель довольно проста, так что вы могли бы научить меня, как это сделать? это?

1 Ответ

0 голосов
/ 21 октября 2019

Боюсь, это станет очень самоуверенным. Во всяком случае, я пытаюсь что-то.

... что произошло до того, как я понял, что на самом деле обеспечивает Vavr . Он пытается охватить все упомянутое здесь, например неизменяемые структуры данных и синтаксическое монадное суммирование (с помощью оператора For), и выходит за рамки этого, предлагая даже сопоставление с шаблоном . Он берет полный набор концепций FP и перестраивает их, используя Java, и неудивительно, что Scala приходит в голову, увидев это (« Vavr очень вдохновлен Scala »).

ТеперьОсновы функционального программирования не могут быть освещены одним постом SO. И может быть проблематично познакомиться с ними на языке, подобном Java, который не предназначен для этого. Поэтому, возможно, лучше подходить к ним в их естественной среде обитания, например, к языку Scala , который все еще находится в некоторой близости от Java, или к Haskell , которого нет.

Возвращение из этого объезда с применением функций Вавра может быть более прямым шагом для посвященных. Но, скорее всего, не для разработчика Java, сидящего рядом с вами в офисе, который менее готов пойти на все, и выдвигает аргументы, которые нельзя просто отклонить, как этот: «Если бы мы хотели этого,мы были бы магазином Scala ". Поэтому я бы сказал, что применение Vavr требует прагматичного отношения.

Чтобы подтвердить аргумент Vavra-Scala, давайте возьмем конструкцию Vavra For (все List s упомянуты io.vavr.collection.List), она выглядит следующим образом:

Iterator<Tuple2<Integer, String>> tuples =
            For(List.of(1, 2, 3), i ->
                    For(List.of(4, 5, 6))
                            .yield(a -> Tuple.of(i, String.valueOf(a))));

В Scalaвы встретите For и yield таким образом.

val tuples = for {
                  i <- 1 to 3
                  a <- 4 to 6
             } yield (i, String.valueOf(a))

Вся техника монады остается под капотом, где Вавра приносит больше приближения, обязательно протекает некоторое количество внутренних органов. Для целей изучения было бы странно начать с гибридных существ Вавры.

Итак, что осталось от моего поста, так это небольшая временная обработка некоторых основ FP, используя пример ОП, подробно описывающий неизменность и Try на уровне траншеи, но без сопоставления с образцом. Здесь мы идем:

Одной из определяющих характеристик FP являются функции без побочных эффектов («чистые функции»), которые, естественно, (так сказать) идут вместе с неизменяемыми структурами данных /объекты, которые могут показаться странными. Одно очевидное преимущество в том, что вам не нужно беспокоиться, что ваши операции приводят к непреднамеренным изменениям в другом месте. Но Java никоим образом не навязывает это, а также неизменных коллекций только на поверхностном уровне. Из характеристик подписи FP только Java предлагает функции более высокого порядка с java-lambdas.

Я довольно часто использовал функциональный стиль при работе со сложными структурами, где придерживался этих двух принципов. Например, загрузить дерево T объектов из базы данных, выполнить некоторые преобразования в нем, что означало создание другого дерева объектов T ', что-то вроде одной большой операции map, поместить изменения перед пользователем, чтобы принять или отклонить их. Если они приняты, примените изменения к связанным сущностям JPA и сохраните их. Поэтому после функционального преобразования были применены две мутации.

Я бы предложил применить FP в этом смысле и попытался сформулировать соответствующую версию вашего кода, используя неизменный класс DocVersion. Я решил упростить часть метаданных для примера.

Я также попытался подчеркнуть, как можно сформулировать и использовать еще один "без исключения" Try подход (часть из него браконьерская отсюда ). Это небольшая версия Vavr Try , с надеждой сосредоточиться на самом главном. Обратите внимание на его близость к Java Необязательным и методам map и flatMap, что делает его воплощением концепции FP, называемой monad . Несколько лет назад он стал печально известным из-за очень запутанных постов в блоге, обычно начинающихся с «Что такое монада?». (например, этот ). Они стоили мне нескольких недель моей жизни, в то время как довольно просто получить хорошее представление об этой проблеме, просто используя потоки Java или Optionals. * * * * * * * * * * * * * * of * * * * * * * * * * * * *1068* * * * * * *1068* * * * * * * *1068* * * * *

* * * * * * *1066* * * * *1066* Scale *. map и flatMap, Try будут, грубо говоря, подходить для добавления синтаксиса, как вы найдете его в C # ( linq-выражений ) или Scala для-выражений ,В Java нет аналога, но некоторые попытки хотя бы компенсировать бит перечислены здесь , а Vavr выглядит как другая. Лично я время от времени использую библиотеку jool .

Передача потоков в качестве результатов функции кажется мне не совсем канонической, поскольку потоки не должны использоваться повторно . Это также причина для создания списка в качестве промежуточного результата в process().

public class PathHandler {

class DocVersionDAO {
    public void save(DocVersion v) {

    }

    public DocVersion validate(DocVersion v) {
        return v;
    }

    public Stream<DocVersion> getAllForPathProcessing() {
        return null;
    }
}

class Metadata {
    @Id
    private final Long id;
    private final String value;

    Metadata() {
        this.id = null;
        this.value = null;
    }

    Metadata(Long id, String value) {
        this.id = id;
        this.value = value;
    }

    public Optional<String> getValue() {
        return Optional.of(value);
    }

    public Metadata withValue(String value) {
        return new Metadata(id, value);
    }

}

public @interface Id {
}

class DocVersion {
    @Id
    private Long id;

    private final Metadata metadatata;

    public Metadata getMetadatata() {
        return metadatata;
    }

    public DocVersion(Long id) {
        this.id = id;
        this.metadatata = new Metadata();
    }

    public DocVersion(Long id, Metadata metadatata) {
        this.id = id;
        this.metadatata = metadatata;
    }

    public DocVersion withMetadatata(Metadata metadatata) {
        return new DocVersion(id, metadatata);
    }

    public DocVersion withMetadatata(String metadatata) {
        return new DocVersion(id, this.metadatata.withValue(metadatata));
    }
}

private DocVersionDAO dao;


public List<DocVersion> process() {

    List<Tuple2<DocVersion, Try<DocVersion>>> maybePersisted = dao.getAllForPathProcessing()
            .map(d -> augmentMetadata(d, LocalDateTime.now().toString()))
            .map(d -> Tuple.of(d, Try.of(() -> dao.validate(d))
                    .flatMap(this::trySave)))
            .peek(i -> i._2.onException(this::logExceptionWithBadPracticeOfUsingPeek))
            .collect(Collectors.toList());

    maybePersisted.stream()
            .filter(i -> i._2.getException().isPresent())
            .map(e -> String.format("Item %s caused exception %s", e._1.toString(), fmtException(e._2.getException().get())))
            .forEach(this::log);

    return maybePersisted.stream()
            .filter(i -> !i._2.getException().isPresent())
            .map(i -> i._2.get())
            .collect(Collectors.toList());
}

private void logExceptionWithBadPracticeOfUsingPeek(Exception exception) {
    logException(exception);
}

private String fmtException(Exception e) {
    return null;
}

private void logException(Exception e) {
    log(fmtException(e));
}

public DocVersion augmentMetadata(DocVersion v, String augment) {
    v.getMetadatata().getValue()
            .ifPresent(m -> log(String.format("Doc %d has matadata %s, augmenting it with %s", v.id, m, augment)));

    return v.withMetadatata(v.metadatata.withValue(v.getMetadatata().value + augment));
}

public Try<DocVersion> trySave(DocVersion v) {
    return new Try<>(() -> {
        dao.save(v);
        return v;
    });
}

private void log(String what) {
}

}

Попробуйте выглядеть так

public class Try<T> {
    private T result;
    private Exception exception;

    private Try(T result, Exception exception) {
        this.result = result;
        this.exception = exception;
    }

    public static <T> Try<T> of(Supplier<T> f)
    {
        return new Try<>(f);
    }

    T get() {
        if (result == null) {
            throw new IllegalStateException();
        }
        return result;
    }

    public void onException(Consumer<Exception> handler)
    {
        if (exception != null)
        {
            handler.accept(exception);
        }
    }

    public <U> Try<U> map(Function<T, U> mapper) {
        return exception != null ? new Try<>(null, exception) : new Try<>(() -> mapper.apply(result));
    }

    public <U> Try<U> flatMap(Function<T, Try<U>> mapper) {
        return exception != null ? null : mapper.apply(result);
    }

    public void onError(Consumer<Exception> exceptionHandler) {
        if (exception != null) {
            exceptionHandler.accept(exception);
        }
    }

    public Optional<Exception> getException() {
        return Optional.of(exception);
    }

    public Try(Supplier<T> r) {
        try {
            result = r.get();
        } catch (Exception e) {
            exception = e;
        }
    }
}
...