Entonces, después de jugar con los genéricos de Java un poco, para obtener una comprensión más profunda de sus capacidades, decidí tratar de implementar la versión curried de la función de composición, familiar para funcional programadores. Compose tiene el tipo (en idiomas funcionales) (b -> c) -> (a -> b) -> (a -> c)
. Hacer las funciones aritméticas de currículum no fue demasiado difícil, ya que son solo polimórficas, pero componer es una función de orden superior, y su comprensión de los genéricos en Java es comprobadamente comprobada.Abusar genéricos para implementar una función de composición al curry en Java
Aquí es la aplicación que he creado hasta ahora:
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);
}
Las implementaciones del complemento, mult, y componer todo compilar muy bien, pero me encuentro con un problema cuando se trata de utilizar realmente componer . Me sale el siguiente error de la línea 12 (el primer uso de de composición en principal):
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);
Asumo este error se debe a que los tipos genéricos son invariantes, pero no estoy seguro de cómo resolver el problema. Por lo que he leído, las variables de tipo comodín se pueden usar para aliviar la invarianza en algunos casos, pero no estoy seguro de cómo usarlo aquí o incluso si será útil.
Descargo de responsabilidad: No tengo intención de escribir código como este en ningún proyecto real. Este es un tipo de cosas divertidas de "se puede hacer". Además, hice que los nombres de las variables fueran breves, desafiando la práctica estándar de Java porque de lo contrario este ejemplo se convierte en aún más de una pared de texto incomprensible.
Ok!¡Gracias a su respuesta creo que encontré una solución que resuelve el problema! – deontologician
Creo que esa es la solución que satisface mis requisitos de la mejor manera (la versión no inferida). No se deduce, pero creo que no vale la pena definir una clase extra. – deontologician