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