Боюсь, это станет очень самоуверенным. Во всяком случае, я пытаюсь что-то.
... что произошло до того, как я понял, что на самом деле обеспечивает 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;
}
}
}