Pregunta

Es algo como esto posible hacerlo en Java?

for (Object o : objects) {
  for (Function f : functions) {
    f(o);
  }
}

Sólo estoy llamando a un puñado de funciones, pero necesito a componerlos, así:

for (Object o : objects) {
  for (Function f : functions) {
    for (Function g : functions) {
      f(g(o));
    }
  }
}

Y me gustaría evitar escribir cientos de líneas de llamadas a funciones.

He intentado investigar los punteros de función y funtores, pero no he encontrado nada pertinente.

¿Fue útil?

Solución

No se puede utilizar la sintaxis f(g(o)), pero se puede utilizar (con una adecuada interfaz) f.call(g.call(o)).

public interface UnaryFunction<Arg, Ret> {
    Ret call(Arg arg);
}

Ejemplo de uso (esto es lo más cerca que se puede llegar a funtores en Java, por lo menos hasta el cierre lo hacen en el idioma):

public class Exp implements UnaryFunction<Double, Double> {
    public Double call(Double arg) {
        return Math.exp(arg);
    }
}

Si no desea crear un trillón de clases, un enfoque basado en la reflexión puede funcionar mejor (por ejemplo double -> funciones double en java.lang.Math, pero fácilmente adaptable a otros escenarios):

public class MathUnary implements UnaryFunction<Double, Double> {
    private final Method method;

    public MathUnary(String funName) {
        try {
            method = Math.class.getMethod(funName, double.class);
        } catch (NoSuchMethodException exc) {
            throw new IllegalArgumentException(exc);
        }
        if (method.getReturnType() != double.class)
            throw new IllegalArgumentException();
    }

    public Double call(Double arg) {
        try {
            return (Double) method.invoke(null, arg);
        } catch (IllegalAccessException exc) {
            throw new AssertionError(exc);
        } catch (InvocationTargetException exc) {
            throw new AssertionError(exc);
        }
    }
}

(Los mensajes de excepción se han quedado fuera por razones de brevedad. Obviamente, me gustaría poner en producción para el código.)

Ejemplo de uso:

MathUnary[] ops = {
    new MathUnary("sin"), new MathUnary("cos"), new MathUnary("tan")
};

for (UnaryFunction<Double, Double> op1 : ops) {
    for (UnaryFunction<Double, Double> op2 : ops) {
        op1.call(op2.call(arg));
    }
}

Otros consejos

Java en realidad no hace funtores con exactitud, pero se puede llegar muy cerca de una interfaz. Yo recomendaría tal vez tratando de algo como esto.

public interface Function {
    Object doWork(Object o);
}

public class Function1 implements Function {
    public Object doWork(Object o) {
        ...
    }
}

...

Y a continuación, en su código que crearía una matriz o lista que contiene Función1, Función2 ... objetos y ejecutar algo que se parece mucho a su código.

for (Object o : objects) {
      for (Function f : functionList) {
          f.doWork(o);
      }
}

O, para dos niveles de anidamiento:

for (Object o : objects) {
      for (Function f : functionList1) {
            for (Function g : functionList2) {
                f.doWork(g.doWork(o));
            }
      }
}

@Seth - Aquí está su ejemplo con los genéricos. Dado que no existen genéricos en tiempo de ejecución, no entiendo por qué temer una pérdida de "flexibilidad". Si utiliza los genéricos, entonces usted está usando objetos.

Si desea el comportamiento de F a variar en función del tipo de retorno de G, entonces sólo le declare su F a hacer algo como F, fácil peasy.

//=== Function.java

public interface Function<ReturnType, Type> {
    ReturnType doWork(Type arg);
}

//=== SomethingWeird.java

import java.util.*;

// yo dawg, i heard you liked functions.  so i put a function in yo'
// function, so you can derive while you derive.
public class SomethingWeird {
    public static <FReturnType, FType, GType> List<FReturnType> collateOrSomething(
        Iterable<GType> objects,
        Iterable<Function<FReturnType, FType>> fList,
        Iterable<Function<FType, GType>> gList
    ) {
        List<FReturnType> results = new ArrayList<FReturnType>();
        for (GType garg : objects) {
            for (Function<FReturnType, FType> f : fList) {
                for (Function<FType, GType> g : gList) {
                    results.add(f.doWork(g.doWork(garg)));
                }
            }
        }
        return results;
    }
}

//=== SomethingWeirdTest.java

import java.util.*;

import org.junit.*;
import static org.junit.Assert.*;

public class SomethingWeirdTest {
    // this is kinda silly, and...
    public static class F1 implements Function<Integer, Double> {
        @Override
        public Integer doWork(Double arg) {
            return arg.intValue();
        }

    }

    // ...this has all kinds of autoboxing madness, but...
    public static class F2 implements Function<Integer, Double> {
        @Override
        public Integer doWork(Double arg) {
            double ceil = Math.ceil(arg);
            return (int) ceil;
        }       
    }


    // ...why you'd want to do something like this is quite beyond me...
    public static class G1 implements Function<Double, String> {
        @Override
        public Double doWork(String arg) {
            return Math.PI * arg.length();
        }
    }

    // ...ditto this...
    public static class G2 implements Function<Double, String> {
        @Override
        public Double doWork(String arg) {
            return Math.E * arg.length();
        }

    }

    // oh, yeah, it was so we could test this weird thing
    @Test
    public void testCollateOrSomething() {
        List<String> data = Arrays.asList("x", "xx", "xxx");
        List<Function<Integer, Double>> fList = Arrays.asList(new F1(), new F2());
        List<Function<Double, String>> gList = Arrays.asList(new G1(), new G2());
        List<Integer> results = SomethingWeird.collateOrSomething(data, fList, gList);

        assertEquals(12, results.size());

        // x
        assertEquals(3, (int) results.get(0));
        assertEquals(2, (int) results.get(1));
        assertEquals(4, (int) results.get(2));
        assertEquals(3, (int) results.get(3));

        // xx
        assertEquals(6, (int) results.get(4));
        assertEquals(5, (int) results.get(5));
        assertEquals(7, (int) results.get(6));
        assertEquals(6, (int) results.get(7));

        // xxx
        assertEquals(9, (int) results.get(8));
        assertEquals(8, (int) results.get(9));
        assertEquals(10, (int) results.get(10));
        assertEquals(9, (int) results.get(11));
    }
}
scroll top