Итак, немного поигравшись с дженериками 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, потому что в противном случае этот пример станет даже более непонятной стеной текста.