Vejo java.util.function.BiFunction, então posso fazer isso:
BiFunction<Integer, Integer, Integer> f = (x, y) -> { return 0; };
E se isso não for bom o suficiente e eu precisar do TriFunction? Não existe!
TriFunction<Integer, Integer, Integer, Integer> f = (x, y, z) -> { return 0; };
Acho que devo acrescentar que sei que posso definir meu próprio TriFunction, estou apenas tentando entender a razão por trás de não incluí-lo na biblioteca padrão.
Respostas:
Pelo que eu sei, existem apenas dois tipos de funções, destrutivas e construtivas.
Enquanto a função construtiva, como o nome indica, constrói algo, uma função destrutiva destrói algo, mas não da maneira que você pode pensar agora.
Por exemplo, a função
é construtivo . Como você precisa construir algo. No exemplo, você construiu a tupla (x, y) . As funções construtivas têm o problema de não serem capazes de lidar com argumentos infinitos. Mas o pior é que você não pode simplesmente deixar uma discussão aberta. Você não pode simplesmente dizer "bem, deixe x: = 1" e experimentar todos os y que desejar. Você tem que construir a cada vez toda a tupla com
x := 1
. Então, se você quiser ver o que as funções retornam,y := 1, y := 2, y := 3
você precisa escreverf(1,1) , f(1,2) , f(1,3)
.No Java 8, as funções construtivas devem ser tratadas (na maioria das vezes) usando referências de método porque não há muita vantagem em usar uma função lambda construtiva. Eles são um pouco como métodos estáticos. Você pode usá-los, mas eles não têm estado real.
O outro tipo é o destrutivo, pega algo e desmonta na medida do necessário. Por exemplo, a função destrutiva
faz o mesmo que a função
f
que foi construtiva. Os benefícios de uma função destrutiva são: agora você pode lidar com infinitos argumentos, o que é especialmente conveniente para fluxos, e pode simplesmente deixar os argumentos abertos. Portanto, se você quiser ver novamente como seria o resultado sex := 1
ey := 1 , y := 2 , y := 3
, você pode dizerh = g(1)
eh(1)
é o resultado paray := 1
,h(2)
paray := 2
eh(3)
paray := 3
.Então aqui você tem um estado fixo! Isso é bastante dinâmico e na maioria das vezes é o que queremos de um lambda.
Padrões como o Factory são muito mais fáceis se você puder apenas incluir uma função que faça o trabalho para você.
Os destrutivos são facilmente combinados uns com os outros. Se o tipo estiver certo, você pode apenas compor como quiser. Usando isso, você pode definir facilmente morfismos que tornam (com valores imutáveis) o teste muito mais fácil!
Você também pode fazer isso com uma composição construtiva, mas a composição destrutiva parece mais bonita e mais parecida com uma lista ou um decorador, e a construtiva se parece muito com uma árvore. E coisas como voltar atrás com funções construtivas simplesmente não são legais. Você pode apenas salvar as funções parciais de uma destrutiva (programação dinâmica), e no "retrocesso" apenas usar a função destrutiva antiga. Isso torna o código muito menor e mais legível. Com funções construtivas, você tem mais ou menos para se lembrar de todos os argumentos, o que pode ser muito.
Então, por que é necessário
BiFunction
haver mais perguntas do que por que não háTriFunction
?Primeiro de tudo, muito tempo você tem apenas alguns valores (menos de 3) e precisa apenas de um resultado, então uma função destrutiva normal não seria necessária, uma função construtiva seria suficiente. E há coisas como mônadas que realmente precisam de uma função construtiva. Mas, fora isso, não há realmente muitas boas razões para haver um
BiFunction
. O que não significa que deva ser removido! Eu luto por minhas Mônadas até morrer!Portanto, se você tiver muitos argumentos, que não podem ser combinados em uma classe de contêiner lógica, e se precisar que a função seja construtiva, use uma referência de método. Caso contrário, tente usar a nova habilidade adquirida de funções destrutivas, você pode se ver fazendo muitas coisas com muito menos linhas de código.
fonte
BiFunction
foi criado para permitir uma redução fácil de dados e a maioria dasStream
operações de terminal são apenas reduções de dados. Um bom exemplo éBinaryOperator<T>
, usado em muitosCollectors
. Um primeiro elemento é reduzido com o segundo, que pode então ser reduzido com o próximo, e assim por diante. Claro, você pode criar umFunction<T, Function<T, T>
func = x -> (y -> / * código de redução aqui * /). Mas seriamente? Tudo isso quando você pode simplesmente fazerBinaryOperator<T> func = (x, y) -> /*reduction code here*/
. Além disso, essa abordagem de redução de dados se parece muito com sua abordagem "destrutiva" para mim.Function<Integer,Integer> f = (x,y) -> x + y
Java é válido, o que não é. Isso deveria ser um BiFunction para começar!Se você precisar do TriFunction, basta fazer o seguinte:
A seguir, um pequeno programa mostra como ele pode ser usado. Lembre-se de que o tipo de resultado é especificado como um último parâmetro de tipo genérico.
Eu acho que se houvesse um uso prático para TriFunction em
java.util.*
oujava.lang.*
ele teria sido definido. Eu nunca iria além de 22 argumentos ;-) O que quero dizer com isso, todo código novo que permite fazer streaming de coleções nunca exigiu TriFunction como qualquer um dos parâmetros do método. Portanto, não foi incluído.ATUALIZAR
Para completar e seguir a explicação das funções destrutivas em outra resposta (relacionada ao currying), veja como o TriFunction pode ser emulado sem interface adicional:
Claro, é possível combinar funções de outras maneiras, por exemplo:
Embora currying seja natural para qualquer linguagem que suporte programação funcional além de lambdas, Java não é construído dessa forma e, embora seja possível, o código é difícil de manter e às vezes de ler. No entanto, é muito útil como exercício e, às vezes, funções parciais têm um lugar legítimo em seu código.
fonte
TriFunction<Integer,Integer,Integer,Integer> comp = (x,y,z) -> x + y + z; comp = comp.andThen(s -> s * 2); int result = comp.apply(1, 2, 3); //12
Consulte stackoverflow.com/questions/19834611/…andThen()
exemplo de uso à resposta.BiFunction
é usado naStream
API para realizar redução de dados, que se parece muito com a abordagem currying para mim: você nunca toma mais do que dois argumentos, e você pode processar qualquer número de elementos, uma redução de cada vez (veja meu comentário sobre a resposta aceita, ficaria feliz em saber se estou errado em ver dessa forma).A alternativa é adicionar a dependência abaixo,
Agora, você pode usar a função Vavr, como abaixo de até 8 argumentos,
3 argumentos:
5 argumentos:
fonte
vavr
biblioteca - ela torna a programação de estilo funcional o mais suportável possível em Java.Tenho quase a mesma pergunta e uma resposta parcial. Não tenho certeza se a resposta construtiva / desconstrutiva é o que os designers da linguagem tinham em mente. Acho que ter 3 e mais até N tem casos de uso válidos.
Eu venho do .NET. e no .NET você tem Func e Action para funções void. Predicado e alguns outros casos especiais também existem. Consulte: https://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx
Eu me pergunto qual foi a razão pela qual os designers de linguagem optaram por Function, Bifunction e não continuaram até DecaExiFunction?
A resposta à segunda parte é o apagamento de tipo. Após a compilação, não há diferença entre Func e Func. Portanto, o seguinte não compila:
Funções internas foram usadas para contornar outro problema menor. O Eclipse insistiu em ter ambas as classes em arquivos denominados Function no mesmo diretório ... Não tenho certeza se isso é um problema do compilador hoje em dia. Mas não posso virar o erro de no Eclipse.
Func foi usado para evitar conflitos de nome com o tipo de Função java.
Portanto, se você deseja adicionar Func de 3 a 16 argumentos, pode fazer duas coisas.
Exemplo para a segunda maneira:
e
Qual seria a melhor abordagem?
Nos exemplos acima, não incluí implementações para os métodos andThen () e compose (). Se você adicionar esses, deve adicionar 16 sobrecargas cada: o TriFunc deve ter um andthen () com 16 argumentos. Isso geraria um erro de compilação devido às dependências circulares. Além disso, você não teria essas sobrecargas para Function e BiFunction. Portanto, você também deve definir Func com um argumento e Func com dois argumentos. Em .NET, as dependências circulares seriam contornadas pelo uso de métodos de extensão que não estão presentes em Java.
fonte
andThen
com 16 argumentos? O resultado de uma função em Java é um valor único.andThen
pega esse valor e faz algo com ele. Além disso, não há problema com a nomenclatura. Os nomes das classes devem ser diferentes e estar em arquivos diferentes com o mesmo nome - seguindo a lógica definida pelos desenvolvedores da linguagem Java com Function e BiFunction. Além disso, todos esses nomes diferentes são necessários se os tipos de argumento forem diferentes. Pode-se criar umVargFunction(T, R) { R apply(T.. t) ... }
único tipo.Encontrei o código-fonte do BiFunction aqui:
https://github.com/JetBrains/jdk8u_jdk/blob/master/src/share/classes/java/util/function/BiFunction.java
Eu modifiquei para criar o TriFunction. Como BiFunction, ele usa andThen () e não compose (), portanto, para alguns aplicativos que requerem compose (), pode não ser apropriado. Deve servir para tipos normais de objetos. Um bom artigo sobre andThen () e compose () pode ser encontrado aqui:
http://www.deadcoderising.com/2015-09-07-java-8-functional-composition-using-compose-and-andthen/
fonte
Você também pode criar sua própria função usando os 3 parâmetros
fonte
Você nem sempre pode parar no TriFunction. Às vezes, você pode precisar passar n número de parâmetros para suas funções. Então a equipe de suporte terá que criar um QuadFunction para consertar seu código. A solução de longo prazo seria criar um Objeto com os parâmetros extras e, em seguida, usar a Função ou BiFunção pronta.
fonte