Злоупотребление дженериками для реализации каррированной функции композиции в Java

Итак, немного поигравшись с дженериками Java, чтобы глубже понять их возможности, я решил попробовать реализовать каррированную версию функции композиции, знакомую функциональным программистам. Compose имеет тип (в функциональных языках) (b -> c) -> (a -> b) -> (a -> c) . Выполнение арифметических функций каррирования не было слишком сложным, поскольку они просто полиморфны, но 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);
}

Все реализации add, mult и compose компилируются отлично, но у меня возникают проблемы, когда дело доходит до использования compose. Я получаю следующую ошибку для строки 12 (первое использование compose в main):

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

Я предполагаю, что эта ошибка вызвана тем, что универсальные типы инвариантны, но я не уверен, как решить проблему. Из того, что я читал, в некоторых случаях можно использовать переменные с подстановочными знаками для уменьшения инвариантности, но я не уверен, как это использовать здесь и даже будет ли это полезно.

Заявление об ограничении ответственности: я не собираюсь писать подобный код в каком-либо реальном проекте. Это забавная вещь типа "можно ли это сделать".Кроме того, я сделал имена переменных краткими, вопреки стандартной практике Java, потому что в противном случае этот пример станет даже более непонятной стеной текста.

6
задан deontologician 18 January 2012 в 17:06
поделиться