【问题标题】:Logging in functional programming with Java使用 Java 进行函数式编程
【发布时间】:2014-10-02 19:46:31
【问题描述】:

我是函数式编程的新手,我正在尝试使用 Java 中的 Lambda 功能来尝试执行 FP。我知道 Java 不是学习函数式的好选择,但在我的办公室里我只能使用 Java,我很想在那里应用其中的一些原则。

我在 Java 中创建了一个 Optional 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();
    }
}

我使用它来避免在我的代码中嵌套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

【问题讨论】:

  • 为什么要重新发明 Optional? Java 8 已经引入this class
  • @Eran 我这样做只是为了学习单子类型的基本原理。只是为了好玩。
  • 所以你想计算哪个引用与计数器一起使用?
  • @PeterLawrey 也许我解释不正确,实际上我需要知道的是哪个参考被跳过,哪个被使用。用于调试级别的日志。
  • 你能把你所有的代码贴出来让我/我们玩玩吗?例如 or() 在哪里定义?

标签: java functional-programming java-8 monads


【解决方案1】:

我会使用 varargs 方法,它会更容易跟踪和编写简短。

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

【讨论】:

  • 是的,我可以这样做,但这不允许我拥有特定于上下文的日志。如果我在两种不同的情况下使用 optional ,我需要两个在那种情况下有意义的不同日志。所以我想如果 Logging 也可以作为组合来完成。这样做会使 Optional 做日志记录,这是正交职责。也许我错了,让事情变得混乱。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

    一个很好的解决方案是幺半群组合。

    组合幺半群

    monoid 是具有标识的关联二元运算。您的 Optional&lt;A&gt; 类型为任何 A 形成一个幺半群:

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

    在您的情况下,Monoid&lt;Optional&lt;A&gt;&gt; 将使用or 作为sumAbsent 作为zero 来实现,因此yourMonoid.sum(x, y) 应该与x.or(y) 相同。

    现在您想将它与另一个幺半群组合——一个由您的日志组成的幺半群。因此,假设您使用简单的String 作为日志。好吧,String 形成一个幺半群,其中sum 是字符串连接,zero 是空字符串。

    您想将String 半群与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&lt;Option&lt;A&gt;, String&gt; 而不仅仅是Option&lt;A&gt;,并且这种类型不带有flatMap。你真正想要的(如果 Java 可以做到,但它不能)是使用 Writer&lt;String, _&gt; monad 和像 OptionT 这样的 monad 转换器。想象一下 Java 可以有 monad 转换器,P2&lt;Option&lt;A&gt;, String&gt; 类型将等同于OptionT&lt;Writer&lt;String, _&gt;, A&gt; 类型,其中_ 表示部分应用的类型构造函数(显然不是有效的 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))
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2018-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-30
      • 1970-01-01
      • 2020-03-19
      相关资源
      最近更新 更多