使用Java进行函数式编程中的登录记录

5

我对函数编程比较新,而我正试图使用Java中的Lambda功能来尝试进行函数编程。我知道Java不是学习函数编程的好选择,但在我的办公室里,我只能使用Java,并且希望能够在那里应用一些这些原则。

我在Java中创建了一个类似于Optional单子类型的东西,其代码如下:

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();
    }
}

我使用这个方法来避免在我的代码中嵌套检查null值,一个典型的应用场景是我需要类似于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


1
你为什么要重新发明Optional?Java 8已经引入了这个类(http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)。 - Eran
@Eran 我这样做只是为了学习单子类型的基本原理。只是为了好玩。 - Narendra Pathai
所以你想用计数器来统计哪个引用被使用了? - Peter Lawrey
@PeterLawrey,也许我解释得不太清楚,实际上我需要知道的是哪个引用被跳过了,哪个被使用了。这是为了调试级别的日志。 - Narendra Pathai
你能把你的所有代码都发布出来,这样我/我们就可以玩一下吗?例如,or()是在哪里定义的? - Xabster
@Xabster附上了我的gist链接。 - Narendra Pathai
3个回答

2
一种简洁的解决方案是单子合成。
组合单子 单子是具有单位元和可结合二元操作的代数结构。对于任何类型A,您的Optional<A>类型形成该类型的一个单子:

https://functionaljava.ci.cloudbees.com/job/master/javadoc/fj/Monoid.html#firstOptionMonoid--

在您的情况下,Monoid<Optional<A>> 将使用 or 作为 sum,使用 Absent 作为 zero 进行实现,因此 yourMonoid.sum(x, y) 应该与 x.or(y) 相同。
现在,您想将其与另一个单子结合起来--其中包括您的日志。所以假设您使用一个简单的 String 作为您的日志。那么,String 形成一个单子,其中 sum 是字符串连接,zero 是空字符串。
您想将 String 单子与 firstOptionMonoid 结合起来。为此,您需要一个元组类型。Functional Java 有一个称为 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中学习,我写了一本书叫做《Scala函数式编程》其中关于单子的章节可以免费在线阅读组合单子 但现在您正在使用复合类型P2<Option<A>, String>,而不仅仅是Option<A>,此类型没有flatMap。如果Java可以这样做(但它不能),您真正想要的是使用像OptionT这样的单子变换器与Writer<String, _>单子。暂时想象一下,如果Java有单子变换器,类型P2<Option<A>, String>将等同于类型OptionT<Writer<String, _>, A>,其中_表示部分应用的类型构造函数(显然不是有效的Java)。

Java中唯一的解决方案是以一阶方式组合这些单子:

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))
  }
}

0

目前,您有两种Optional的实现,PresentAbsent。我建议引入另一种类型,LoggingAbsent,它的行为类似于Absent,但会记录任何具有回退行为的操作。

关键是决定何时返回这些类型中的哪一个。例如,将可空值转换为Optional时,返回LoggingAbsent实例会很有用。但是,在Optional上调用map时,LoggingAbsent实现记录操作并返回非记录的Absent作为结果,以便链的第一个操作报告回退,而所有后续操作都会静默回退。

您还可以通过提供创建记录和非记录Optional的替代工厂方法来支持显式选择。


0
我会使用可变参数的方法,这样更容易跟踪和编写简短。
public static <T> Optional<T> of(T... value) {
    for(int i=0;i<values.length;i++) {
        if (value[i] != null) {
            // log that value[i] was chosen
            return new Present<T>(value[i]);
        }
    }
    // log all was null
    return Absent.<T>instance();
}

在你的例子中

String value = Optional.of(null, null, "Hello").get();

是的,我可以这样做,但这将不允许我拥有特定上下文的日志。如果我在两种不同的情况下使用可选项,则需要两个在该情况下有意义的不同日志。因此,我认为如果记录也可以作为组合完成。这样做使得可选项执行正交责任的记录。也许我错了,混淆了事情。 - Narendra Pathai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接