【问题标题】:Problems with Java Generics while building a monad构建 monad 时 Java 泛型的问题
【发布时间】:2020-05-12 15:04:37
【问题描述】:

我正在使用 Java 代码来创建一个函数式风格的 monad,但是我在使用泛型时感到震惊,如果我不强制转换我的对象,Java 编译器会给我一个编译错误(尽管泛型可以解决这个问题!)

这是用法:

//COMPILATION ERROR! It requires a cast to String
String message = If.of("Hi", s->s!=null).apply(s->s+" guys!").get();

允许:

这是我的单子:

import java.util.function.Function;
import java.util.function.Predicate;

public class If<T, R> {

  private T t;
  private Predicate predicate;

  private If(T t, Predicate predicate) {
    this.t = t;
    this.predicate = predicate;
  }

  public static<T> If of(T t, Predicate predicate) {
    return new If(t, predicate);
  }

  public If<R,R> apply(Function<T, R> function) {
    if(predicate!=null && predicate.test(t)){
        return new If<R, R>(function.apply(t), null);
    }
    return If.of(this.t, null);
  }

  public T get() {
    return t;
  }

}

【问题讨论】:

  • public static&lt;T&gt; If of(T t, Predicate predicate) { -- 返回类型为 raw。
  • 我猜应该是Predicate&lt;T&gt; 左右。包含来自编译器的错误消息会很有帮助...
  • 抛开使用原始类型的问题。这个单子应该做什么? If.of(1, x -&gt; x &gt; 2).apply(Integer::toString).get() 会返回什么?换句话说,当条件为假时会发生什么?你期待null吗?
  • 如果条件为假,则返回 this。例如,它就像一个 mapIf 它本来存在于 Optional monad 中。仅当 Predicate 被验证时才应用转换。
  • 不,get 无法返回原始对象或转换后的对象,因为它们可能是不同的类型。原始对象的类型为T,而转换后的对象的类型为R。你可以在这里使用Either monad,但这违背了If 的目的,对吧?因为您仍然需要检查返回的是哪一个。你实际上想用这个If monad 完成什么?如果这只是 monad 的练习,那么您可以编写许多其他 monad。

标签: java generics functional-programming monads


【解决方案1】:

Andy Turner 的回答直接解释了为什么您当前的代码无法编译,但您的 monad 似乎有一个更根本的问题 - 它不是很有用。

根据您的说法,如果条件为真,对apply 的第一次调用应该返回包装在 monad 中的转换后的对象,或者如果条件为假,则返回包装在 monad 中的原始对象。但是,由于您将 null 作为两种情况的条件,因此对 apply 的任何后续调用都将导致到达第二个 return,因此始终将第一次调用的结果返回给 apply

事实上,既不能返回原始对象,也不能返回转换后的对象(无论如何,这不是一种有用且类型安全的方式)。要做到类型安全,您需要一个Either&lt;If&lt;T&gt;, If&lt;R&gt;&gt;(假设存在这种类型)。但是要从 Either 中提取值,您仍然需要一个 if 语句,这违背了 If&lt;T&gt; 类的目的。

显然,这只是练习编写单子的练习。在这种情况下,我建议您选择另一个 monad 来实现,例如 Either。我也建议你先看看这个post

【讨论】:

    【解决方案2】:

    直接的问题是of方法的返回类型是raw:

    public static<T> If of(T t, Predicate predicate) {
    

    你大概需要它是这样的:

    public static<T> If<T, Something> of(T t, Predicate<T> predicate) {
    

    我建议您不要真的想将R 烘焙到If 的类型中。如果您改为在方法上声明它,那么您可以灵活地将 apply 它设置为您需要的任何类型:

    public class If<T> {
      // ...
    
      public <R> If<R> apply(Function<T, R> function) {
        if(predicate!=null && predicate.test(t)){
          return new If<>(function.apply(t), null);
        }
        return If.of(this.t, null);
      }
    
      // ...
    }
    

    那么你的of签名可以很简单:

    public static<T> If<T> of(T t, Predicate<T> predicate) {
    

    如果您希望 API 更灵活一点,请添加通配符:

    public static<T> If<T> of(T t, Predicate<? super T> predicate) {
    

    public <R> If<R> apply(Function<? super T, ? extends R> function) {
    

    【讨论】:

    • 非常有趣的建议,我已经尝试过了,但是在 apply() 方法的最后一次返回中,它给了我编译错误,因为它返回的是 If 而不是 If。如何解决?
    • 感谢@Sweeper 不是 null 而是这个。这意味着返回 monad 本身。
    • @AlexMawashi 哦,对。在这种情况下,你根本不需要R:你必须返回一个If&lt;T&gt;
    猜你喜欢
    • 1970-01-01
    • 2015-11-10
    • 1970-01-01
    • 1970-01-01
    • 2011-11-04
    相关资源
    最近更新 更多