滥用泛型来实现Java中的curry组合函数

deo*_*ian 6 java generics functional-programming currying

因此,在使用Java泛型之后,为了更深入地了解它们的功能,我决定尝试实现组合函数的curried版本,熟悉函数式程序员.Compose具有类型(在函数语言中)(b -> c) -> (a -> b) -> (a -> c).做currying算术函数并不太难,因为它们只是多态的,但是compose是一个更高阶的函数,并且它证明了我对Java中泛型的理解.

这是我到目前为止创建的实现:

public class Currying {

  public static void main(String[] argv){
    // Basic usage of currying
    System.out.println(add().ap(3).ap(4));
    // Next, lets try (3 * 4) + 2
    // First lets create the (+2) function...
    Fn<Integer, Integer> plus2 = add().ap(2);
    // next, the times 3 function
    Fn<Integer, Integer> times3 = mult().ap(3);
    // now we compose them into a multiply by 2 and add 3 function
    Fn<Integer, Integer> times3plus2 = compose().ap(plus2).ap(times3);
    // now we can put in the final argument and print the result
    // without compose:
    System.out.println(plus2.ap(times3.ap(4)));
    // with compose:
    System.out.println(times3plus2.ap(new Integer(4)));
  }

  public static <A,B,C> 
                Fn<Fn<B,C>, // (b -> c) -> -- f
                Fn<Fn<A,B>, // (a -> b) -> -- g
                Fn<A,C>>>   // (a -> c)
                compose(){
    return new  Fn<Fn<B,C>, 
                Fn<Fn<A,B>, 
                Fn<A,C>>> () {
      public Fn<Fn<A,B>, 
             Fn<A,C>> ap(final Fn<B,C> f){
        return new Fn<Fn<A,B>, 
                   Fn<A,C>>() {
          public Fn<A,C> ap(final Fn<A,B> g){
            return new Fn<A,C>(){
              public C ap(final A a){
                return f.ap(g.ap(a));
              }
            };
          }
        };
      }
    };
  }

  // curried addition
  public static Fn<Integer, Fn<Integer, Integer>> add(){
    return new Fn<Integer, Fn<Integer, Integer>>(){
      public Fn<Integer,Integer> ap(final Integer a) {
        return new Fn<Integer, Integer>() {
          public Integer ap(final Integer b){
            return a + b;
          }
        };
      }
    };
  }

  // curried multiplication
  public static Fn<Integer, Fn<Integer, Integer>> mult(){
    return new Fn<Integer, Fn<Integer, Integer>>(){
      public Fn<Integer,Integer> ap(final Integer a) {
        return new Fn<Integer, Integer>() {
          public Integer ap(final Integer b){
            return a * b;
          }
        };
      }
    };
  }
}

interface Fn<A, B> {
  public B ap(final A a);
}
Run Code Online (Sandbox Code Playgroud)

add,mult和compose的实现都编译得很好,但我发现自己在实际使用compose时遇到了问题.第12行出现以下错误(main中第一次使用compose):

Currying.java:12: ap(Fn<java.lang.Object,java.lang.Object>) in 
Fn<Fn<java.lang.Object,java.lang.Object>,Fn<Fn<java.lang.Object,java.lang.Object>,Fn<java.lang.Object,java.lang.Object>>>
cannot be applied to (Fn<java.lang.Integer,java.lang.Integer>)
    Fn<Integer,Integer> times3plus2 = compose().ap(plus2).ap(times3);
Run Code Online (Sandbox Code Playgroud)

我认为这个错误是因为泛型类型是不变的,但我不知道如何解决这个问题.根据我的阅读,通配符类型变量可用于减轻某些情况下的不变性,但我不确定如何在此使用它,甚至是否有用.

免责声明:我无意在任何实际项目中编写这样的代码.这是一种有趣的"可以做到"的事情.此外,我无视标准Java实践使变量名称变得简短,因为否则这个例子变成了一个难以理解的文本墙.

Rus*_*ser 4

这里的基本问题是,在对 的原始调用中compose(),编译器无法推断 A、B 和 C 的绑定,因此它假设它们都是 Object。您可以通过显式指定类型绑定来修复它:

Fn<Integer, Integer> times3plus2 = 
    Currying.<Integer, Integer, Integer>compose().ap(plus2).ap(times3);
Run Code Online (Sandbox Code Playgroud)

当然,这样你就失去了类型推断带来的清晰度。如果您需要类型推断,您可以定义一些中间类来进行推断:

public static ComposeStart compose() {
    return new ComposeStart();
}

class ComposeStart {
    public <B,C> ComposeContinuation<B,C> ap(Fn<B,C> f) {
        return new ComposeContinuation<B, C>(f);
    }
}

class ComposeContinuation<B, C> {
    private final Fn<B,C> f;

    ComposeContinuation(Fn<B,C> f) {
        this.f = f;
    }

    public <A> Fn<A,C> ap(final Fn<A,B> g) {
        return new Fn<A,C>() {
            public C ap(A a) {
                return f.ap(g.ap(a));
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

然而,柯里化的中间步骤就不再是Fn问题了。