Itérer sur les fonctions
-
18-09-2019 - |
Question
est quelque chose comme ceci possible de le faire en Java?
for (Object o : objects) {
for (Function f : functions) {
f(o);
}
}
Je n'appelle une poignée de fonctions, mais je dois les composer, comme ceci:
for (Object o : objects) {
for (Function f : functions) {
for (Function g : functions) {
f(g(o));
}
}
}
Et je voudrais éviter d'écrire des centaines de lignes d'appels de fonction.
J'ai essayé des recherches sur des pointeurs de fonction et foncteurs, mais ai rien trouvé pertinent.
La solution
Vous ne pouvez pas utiliser la syntaxe f(g(o))
, mais vous pouvez utiliser (avec une interface appropriée) f.call(g.call(o))
.
public interface UnaryFunction<Arg, Ret> {
Ret call(Arg arg);
}
Exemple d'utilisation (ce qui est aussi proche que vous pouvez obtenir à foncteurs en Java, au moins jusqu'à ce que les fermetures font dans la langue):
public class Exp implements UnaryFunction<Double, Double> {
public Double call(Double arg) {
return Math.exp(arg);
}
}
Si vous ne voulez pas créer un cours de zillion, une approche par réflexion peut fonctionner mieux (par exemple pour double
-> Fonctions double
à java.lang.Math
, mais facilement adaptable à d'autres scénarios):
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);
}
}
}
(messages d'exception ont été omis par souci de concision. De toute évidence, je les ai mis dans le code pour la production.)
Utilisation de l'échantillon:
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));
}
}
Autres conseils
Java ne fait pas vraiment foncteurs exactement, mais vous pouvez vous en approcher avec une interface. Je vous recommande d'essayer quelque chose comme cela peut-être.
public interface Function {
Object doWork(Object o);
}
public class Function1 implements Function {
public Object doWork(Object o) {
...
}
}
...
Et puis dans votre code que vous souhaitez créer un tableau ou d'une liste contenant des objets function1 Function2 ... et d'exécuter quelque chose qui ressemble beaucoup à votre code.
for (Object o : objects) {
for (Function f : functionList) {
f.doWork(o);
}
}
Ou, pour deux niveaux d'imbrication:
for (Object o : objects) {
for (Function f : functionList1) {
for (Function g : functionList2) {
f.doWork(g.doWork(o));
}
}
}
@skiwi - Voici votre exemple avec les génériques. Étant donné que les médicaments génériques n'existent pas à l'exécution, je ne comprends pas pourquoi vous craignez une perte de « flexibilité ». Si vous utilisez des médicaments génériques, alors vous êtes juste en utilisant des objets.
Si vous voulez que le comportement de F à varier en fonction du type de retour de G, alors vous simplement déclarer votre F pour faire quelque chose comme F, peasy facile.
//=== 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));
}
}
Peut-être que vous pouvez essayer un couramment qui vous permettra de ces gangs ensemble. Il est peut-être un design agréable, mais je ne peux pas dire de votre exemple.