我是功能编程的新手,我正在尝试使用
Java中的Lambda功能来尝试FP.我知道Java不是学习功能的好选择,但在我的办公室里,我只能使用Java,并希望在那里应用其中的一些原则.
我在Java中创建了一个可选的monad类型的东西,它看起来像这样:
public abstract class Optional<T> implements Monad<T> {
//unit function
public static <T> Optional<T> of(T value) {
return value != null ? new Present<T>(value) : Absent.<T>instance();
}
@Override
public <V> Monad<V> flatMap(Function<T, Monad<V>> function) {
return isPresent() ? function.apply(get()) : Absent.<V>instance();
}
}
我正在使用它来避免代码中的嵌套空值检查,这是我使用它的典型用例,当我需要类似firstNonNull的东西时.
使用:
String value = Optional.<String>of(null)
.or(Optional.<String>of(null)) //call it some reference
.or(Optional.of("Hello")) //other reference
.flatMap(s -> {
return Optional.of(s.toLowerCase());
})
.get();
这是一种魅力.现在问题是如何将日志记录与此结合起来?如果我需要知道使用了哪些参考文件怎么办?如果这些引用附加了一些语义,并且我需要记录未找到此引用,尝试其他选项,这将非常有用.
日志:
some reference is not present and some other business case specific log
这有可能在Java中实现吗?我试图从网上阅读一些可能的解决方案,并找到了Haskell的Writer monad,但我感到困惑,无法跟进.
编辑
链接到gist
最佳答案 一个简洁的解决方案是幺半群组成.
结合幺半群
A monoid是具有标识的关联二进制操作.您的可选< A> type为任何A形成一个monoid:
https://functionaljava.ci.cloudbees.com/job/master/javadoc/fj/Monoid.html#firstOptionMonoid–
在您的情况下,Monoid< Optional< A>>将使用或作为总和和缺席作为零来实现,因此yourMonoid.sum(x,y)应该与x.or(y)相同.
现在你想把它与另一个monoid组合 – 一个由你的日志组成.因此,假设您使用简单的String作为日志.好吧,String形成一个monoid,其中sum是字符串连接,零是空字符串.
您希望将String monoid与firstOptionMonoid组合在一起.为此你需要一个元组类型.功能Java有a tuple type called P2
.这是你如何组合两个幺半群(这应该真的添加到Monoid类;发送拉请求!):
import fj.*;
import static fj.P.p;
import static fj.Monoid.*;
public final <A,B> Monoid<P2<A,B>> compose(Monoid<A> a, Monoid<B> b) {
return monoid(
x -> y -> p(a.sum(x._1, y._1), b.sum(x._2, y._2)),
p(a.zero, b.zero));
}
然后复合幺半群(在FJ中):
Monoid<P2<Option<A>, String>> m = compose(firstOptionMonoid<A>, stringMonoid)
现在,您并不总是想要添加日志.您希望它取决于Option中的值是存在还是不存在.为此你可以编写一个专门的方法:
public final P2<Option<A>, String> loggingOr(
P2<Option<A>, String> soFar,
Option<A> additional,
String msg) {
return soFar._1.isDefined ?
soFar :
m.sum(soFar, p(additional, msg))
}
我建议一般来看更多的幺半群.它们是一种非常通用的工具,它们是为数不多的纯功能构造之一,在Java中实际上是令人愉快的.如果你不介意在Scala学习,我会免费写一本名为Functional Programming in Scala和the chapter on monoids just happens to be available online的书.
结合单子
但现在您正在使用复合类型P2< Option< A>,String>而不仅仅是Option< A>,而且这种类型没有flatMap.您真正想要的是(如果Java可以做到,但它不能),那就是使用Writer< String,_> monad与像OptionT一样的monad变压器.想象一下Java可以有monad变换器,类型P2< Option< A>,String>将等同于OptionT< Writer< String,_>,A>类型,其中_表示部分应用的类型构造函数(显然不是有效的Java).
Java中唯一的解决方案是以一阶方式组合这些monad:
import fj.data.Writer
public abstract class OptionWriter<W, A> {
abstract Writer<W, Option<A>> writer;
public <B> OptionWriter<W, B> flatMap(Function<A, OptionWriter<B>>) {
...
}
public static <M, T> OptionWriter<M, T> unit(t: T) {
return Writer.unit(Option.unit(t))
}
}