0

我正在尝试使用 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));
    }
}

目标很简单,你能教我正确的方法吗?

4

1 回答 1

0

恐怕,这会变得自以为是。无论如何,我尝试一下。

...这发生在我意识到Vavr实际提供的东西之前。它试图涵盖这里提到的所有内容,例如不可变数据结构和 monad 语法糖化(使用 For 语句),甚至通过模式匹配来超越这些 。它需要一套全面的 FP 概念并使用 Java 重新构建它们,Scala 看到这一点并不奇怪(“ Vavr 受到 Scala 的极大启发”)。

现在,单个 SO 帖子无法涵盖函数式编程的基础。用像 Java 这样不适合它的语言来熟悉它们可能会有问题。因此,也许最好在它们的自然栖息地中接近它们,比如仍然接近 Java 的Scala语言,或者不接近 Java 的Haskell

从这条弯路回来,应用 Vavr 的功能对于初学者来说可能更直接。但对于办公室里坐在你旁边的 Java 开发人员来说可能不是这样,他们不太愿意加倍努力,提出不能随便驳回的论点,比如这个:“如果我们想那样做,我们将成为一家 Scala 商店”。因此我会说,应用 Vavr 需要务实的态度。

为了证实 Vavra-Scala 的论点,让我们看一下 Vavra 的For构造(所有List提到的都是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))));

For在 Scala 中,你会遇到yield这种方式。

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

所有的单子机器都保留在引擎盖下,Vavra 带来了更多的近似值,必然会泄漏一些内部结构。出于学习的目的,从 Vavra 的混合生物开始可能会令人费解。

所以我的帖子剩下的就是对一些 FP 基础知识的简短处理,使用 OP 的示例,详细说明不变性和Try沟槽级别,但省略了模式匹配。开始了:

FP的定义特征之一是没有副作用的函数(“纯函数”),它自然(可以这么说)伴随着不可变的数据结构/对象,这听起来可能有点奇怪。一个明显的回报是,您不必担心您的操作会在其他地方产生意想不到的变化。但是 Java 并没有以任何方式强制执行这一点,它的不可变集合也只是表面上的。从 FP 签名特征来看,Java 仅提供带有 java-lambdas 的高阶函数。

我在处理复杂结构的工作中使用了相当多的函数式风格,我坚持这两个原则。例如,从数据库加载对象树 T,对其进行一些转换,这意味着生成另一棵对象树 T',这是一个大map操作,将更改放在用户面前以接受或拒绝它们。如果被接受,则将更改应用到相关的 JPA 实体并保留它们。所以在功能转换之后应用了两个突变。

我建议在这个意义上应用 FP,并尝试使用不可变DocVersion类来制定相应版本的代码。为了示例,我选择简化元数据部分。

我还试图强调,“无异常”Try方法(其中一些是从这里挖出来的)可以如何制定和更多地利用。它是Vavr's Try的一个小版本,希望专注于要领。请注意它与 Java 的Optional以及其中的mapand方法很接近flatMap,这使它成为称为monad的 FP 概念的化身。几年前,它在一系列高度混乱的博客文章中臭名昭著,通常以“什么是单子?”开头。(例如这个)。它们花费了我几个星期的时间,而仅通过使用 Java 流或 Optionals 就很容易对这个问题有一个很好的直觉。米兰利波卡的“Learn Yourself a Haskell For Great Good ”后来在一定程度上对它有所帮助,Martin Odersky 的Scala语言。

粗略地说,拥有of,mapflatMap,Try将有资格获得语法糖,就像您在 C# ( linq-expressions ) 或 Scala for-expressions中找到的那样。在 Java 中没有等价物,但这里列出了一些至少补偿一点的尝试,而Vavr看起来像另一种。我个人偶尔会使用jool库。

在我看来,将流作为函数结果传递似乎不太规范,因为流不应该被重用。这也是创建 List 作为中间结果的原因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;
        }
    }
}
于 2019-10-21T12:44:29.750 回答