恐怕,这会变得自以为是。无论如何,我尝试一下。
...这发生在我意识到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以及其中的map
and方法很接近flatMap
,这使它成为称为monad的 FP 概念的化身。几年前,它在一系列高度混乱的博客文章中臭名昭著,通常以“什么是单子?”开头。(例如这个)。它们花费了我几个星期的时间,而仅通过使用 Java 流或 Optionals 就很容易对这个问题有一个很好的直觉。米兰利波卡的“Learn Yourself a Haskell For Great Good ”后来在一定程度上对它有所帮助,Martin Odersky 的Scala语言。
粗略地说,拥有of
,map
和flatMap
,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;
}
}
}