Pergunta

É algo como isto possível fazer em Java?

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

Eu só estou chamando um punhado de funções, mas eu preciso para compô-los, assim:

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

E eu gostaria de evitar escrever centenas de linhas de chamadas de função.

Eu tentei pesquisando ponteiros de função e functors, mas não encontrei nada pertinente.

Foi útil?

Solução

Você não pode usar a sintaxe f(g(o)), mas você pode usar (com uma interface adequada) f.call(g.call(o)).

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

Exemplo de uso (este é o mais perto que você pode chegar ao functors em Java, pelo menos até fechamento de torná-la para a língua):

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

Se você não quiser criar um aulas zilhão, uma abordagem baseada na reflexão pode funcionar melhor (exemplo para double -> funções double em java.lang.Math, mas facilmente adaptável a outros cenários):

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);
        }
    }
}

(Mensagens de exceção foram deixados de fora por brevidade. Obviamente, eu colocá-los em para o código de produção.)

Exemplo 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));
    }
}

Outras dicas

Java realmente não faz functors exatamente, mas você pode chegar muito perto com uma interface. Eu recomendo talvez tentar algo como isto.

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

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

...

E, em seguida, em seu código que você criar uma matriz ou uma lista contendo Function1, Function2 ... objetos e executar algo que se parece muito com o seu código.

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

Ou, para dois níveis de aninhamento:

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

@Seth - Aqui é o seu exemplo, com os genéricos. Desde genéricos não existem em tempo de execução, eu não entendo por que temer uma perda de "flexibilidade". Se você usar os genéricos, então você está apenas usando objetos.

Se você quiser o comportamento de F a variar de acordo com o tipo de retorno G, então você teria apenas que declarar o seu F para fazer algo como F, peasy fácil.

//=== 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));
    }
}

Talvez você pode tentar um fluente interface de que iria deixá-lo gangue estes juntos. Pode ser um design agradável, mas eu não posso dizer a partir de seu exemplo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top