使用Java登录函数式编程

我是功能编程的新手,我正在尝试使用
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 Scalathe 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))
  }
}
点赞