web-dev-qa-db-fra.com

Java 8: Où est TriFunction (et parent) dans Java.util.function? Ou quelle est l'alternative?

Je vois Java.util.function.BiFunction pour pouvoir faire ceci:

BiFunction<Integer, Integer, Integer> f = (x, y) -> { return 0; };

Que faire si cela ne suffit pas et que j'ai besoin de TriFunction? Ça n'existe pas!

TriFunction<Integer, Integer, Integer, Integer> f = (x, y, z) -> { return 0; };

J'imagine que je devrais ajouter que je sais que je peux définir ma propre fonction TriFunction. J'essaie simplement de comprendre la raison qui sous-tend de ne pas l'inclure dans la bibliothèque standard.

91
Richard Finegan

À ma connaissance, il n’existe que deux types de fonctions, destructives et constructives.

Tandis que la fonction constructive, comme son nom l'indique, construit quelque chose, la destruction est destructrice, mais pas comme vous le pensez maintenant.

Par exemple, la fonction

Function<Integer,Integer> f = (x,y) -> x + y  

est un constructif un. Comme vous avez besoin de construire quelque chose. Dans l'exemple que vous avez construit le tuple (x, y) . Les fonctions constructives ont le problème de ne pas pouvoir gérer des arguments infinis. Mais le pire, c’est que vous ne pouvez pas laisser une dispute ouverte. Vous ne pouvez pas simplement dire "eh bien, laissez x: = 1" et essayez tous les y que vous souhaitez essayer. Vous devez construire à chaque fois le tuple entier avec x := 1. Donc, si vous voulez voir ce que les fonctions renvoient pour y := 1, y := 2, y := 3, Vous devez écrire f(1,1) , f(1,2) , f(1,3).

Dans Java 8, les fonctions constructives doivent être gérées (la plupart du temps) à l'aide de références de méthodes car il n'y a pas grand avantage à utiliser une fonction constructive lambda. Elles ressemblent un peu aux méthodes statiques. Vous pouvez utilisez-les, mais ils n'ont pas de véritable état.

L'autre type est le destructeur, il prend quelque chose et le démantèle aussi loin que nécessaire. Par exemple, la fonction destructive

Function<Integer, Function<Integer, Integer>> g = x -> (y -> x + y) 

fait la même chose que la fonction f qui était constructive. Les avantages d'une fonction destructive sont que vous pouvez maintenant gérer des arguments infinis, ce qui est particulièrement pratique pour les flux, et vous pouvez simplement laisser les arguments ouverts. Donc, si vous voulez à nouveau voir quel serait le résultat si x := 1 Et y := 1 , y := 2 , y := 3, Vous pouvez dire h = g(1) et h(1) est le résultat de y := 1, h(2) pour y := 2 Et h(3) pour y := 3.

Donc ici vous avez un état fixe! C'est assez dynamique et c'est la plupart du temps ce que nous voulons d'un lambda.

Des modèles comme Factory sont beaucoup plus faciles si vous pouvez simplement mettre une fonction qui fait le travail pour vous.

Les destructifs sont facilement combinés les uns avec les autres. Si le type est correct, vous pouvez simplement les composer comme vous le souhaitez. En utilisant cela, vous pouvez facilement définir des morphismes qui facilitent (avec des valeurs immuables) les tests!

Vous pouvez le faire aussi avec une composition constructive, mais une composition destructive ressemble plus à une liste ou à un décorateur, et la composition constructive ressemble beaucoup à un arbre. Et des choses comme revenir en arrière avec des fonctions constructives ne sont tout simplement pas agréables. Vous pouvez simplement sauvegarder les fonctions partielles d'une fonction destructive (programmation dynamique), et sur "backtrack", utilisez simplement l'ancienne fonction destructive. Cela rend le code beaucoup plus petit et mieux lisible. Avec les fonctions constructives, vous devez plus ou moins vous souvenir de tous les arguments, ce qui peut être énorme.

Alors pourquoi y a-t-il un besoin de BiFunction devrait-il être plus question que de savoir pourquoi il n’existe pas de TriFunction?

Tout d’abord, vous n’avez souvent que quelques valeurs (moins de 3) et vous n’avez besoin que d’un résultat. Ainsi, une fonction destructive normale ne serait pas nécessaire du tout, une fonction constructive suffirait. Et il y a des choses comme les monades qui ont vraiment besoin d'une fonction constructive. Mais à part cela, il n'y a pas vraiment beaucoup de bonnes raisons pour lesquelles il existe un BiFunction. Ce qui ne signifie pas qu'il devrait être enlevé! Je me bats pour mes monades jusqu'à ma mort!

Donc, si vous avez beaucoup d'arguments, que vous ne pouvez pas combiner dans une classe de conteneur logique, et si vous avez besoin que la fonction soit constructive, utilisez une référence de méthode. Sinon, essayez d'utiliser la nouvelle capacité acquise des fonctions destructives, vous risquez de vous retrouver à faire beaucoup de choses avec beaucoup moins de lignes de code.

75
user3003859

Si vous avez besoin de TriFunction, procédez comme suit:

@FunctionalInterface
interface TriFunction<A,B,C,R> {

    R apply(A a, B b, C c);

    default <V> TriFunction<A, B, C, V> andThen(
                                Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (A a, B b, C c) -> after.apply(apply(a, b, c));
    }
}

Après le petit programme montre comment il peut être utilisé. Rappelez-vous que le type de résultat est spécifié en tant que dernier paramètre de type générique.

  public class Main {

    public static void main(String[] args) {
        BiFunction<Integer, Long, String> bi = (x,y) -> ""+x+","+y;
        TriFunction<Boolean, Integer, Long, String> tri = (x,y,z) -> ""+x+","+y+","+z;


        System.out.println(bi.apply(1, 2L)); //1,2
        System.out.println(tri.apply(false, 1, 2L)); //false,1,2

        tri = tri.andThen(s -> "["+s+"]");
        System.out.println(tri.apply(true,2,3L)); //[true,2,3]
    }
  }

Je suppose que s’il y avait une utilisation pratique pour TriFunction dans Java.util.* ou Java.lang.* il aurait été défini. Je n'irais jamais au-delà de 22 arguments, cependant ;-) Ce que je veux dire par là, tout nouveau code qui permet de diffuser des collections n'a jamais requis TriFunction en tant que paramètre de méthode. Donc ce n'était pas inclus.

MISE À JOUR

Pour être complet et suivre l'explication des fonctions destructives dans une autre réponse (liée au curry), voici comment émuler TriFunction sans interface supplémentaire:

Function<Integer, Function<Integer, UnaryOperator<Integer>>> tri1 = a -> b -> c -> a + b + c;
System.out.println(tri1.apply(1).apply(2).apply(3)); //prints 6

Bien sûr, il est possible de combiner des fonctions d’une autre manière, par exemple:

BiFunction<Integer, Integer, UnaryOperator<Integer>> tri2 = (a, b) -> c -> a + b + c;
System.out.println(tri2.apply(1, 2).apply(3)); //prints 6
//partial function can be, of course, extracted this way
UnaryOperator partial = tri2.apply(1,2); //this is partial, eq to c -> 1 + 2 + c;
System.out.println(partial.apply(4)); //prints 7
System.out.println(partial.apply(5)); //prints 8

Bien que le curry soit naturel à tout langage prenant en charge la programmation fonctionnelle au-delà de lambdas, Java n’est pas construit de cette façon et, bien que réalisable, le code est difficile à maintenir, et parfois lu. Cependant, il est très utile comme exercice, et parfois des fonctions partielles ont une place légitime dans votre code.

129
Alex Pakka

J'ai presque la même question et une réponse partielle. Je ne sais pas si la réponse constructive/déconstructive correspond à ce que les concepteurs de langage avaient à l’esprit. Je pense qu'avoir 3 ou plus jusqu'à N a des cas d'utilisation valides.

Je viens de .NET. et dans .NET vous avez Func et Action pour les fonctions void. Des prédicats et d'autres cas spéciaux existent également. Voir: https://msdn.Microsoft.com/en-us/library/bb534960 (v = vs.110) .aspx

Je me demande quelle est la raison pour laquelle les concepteurs de langage ont opté pour Function, Bifunction et n’ont pas continué jusqu’à DecaExiFunction?

La réponse à la deuxième partie est l’effacement du type. Après la compilation, il n'y a pas de différence entre Func et Func. Ce qui suit ne compile donc pas:

package eu.hanskruse.trackhacks.joepie;

public class Functions{

    @FunctionalInterface
    public interface Func<T1,T2,T3,R>{
        public R apply(T1 t1,T2 t2,T3 t3);
    }

    @FunctionalInterface
    public interface Func<T1,T2,T3,T4,R>{
        public R apply(T1 t1,T2 t2,T3 t3, T4 t4);
    }
}

Les fonctions internes ont été utilisées pour contourner un autre problème mineur. Eclipse a insisté pour que les deux classes dans les fichiers nommés Function se trouvent dans le même répertoire ... Je ne sais pas s'il s'agit d'un compilateur de nos jours. Mais je ne peux pas retourner l'erreur dans Eclipse.

Func a été utilisé pour empêcher les conflits de noms avec le type de fonction Java.

Donc, si vous voulez ajouter Func de 3 à 16 arguments, vous pouvez faire deux choses.

  • Assurez TriFunc, TesseraFunc, PendeFunc, ... DecaExiFunc etc
    • (Dois-je utiliser le grec ou le latin?)
  • Utilisez des noms de package ou des classes pour rendre les noms différents.

Exemple pour la deuxième voie:

 package eu.hanskruse.trackhacks.joepie.functions.tri;

        @FunctionalInterface
        public interface Func<T1,T2,T3,R>{
            public R apply(T1 t1,T2 t2,T3 t3);
        }

et

package eu.trackhacks.joepie.functions.tessera;

    @FunctionalInterface
    public interface Func<T1,T2,T3,T4,R>{
        public R apply(T1 t1,T2 t2,T3 t3, T4 t4);
    }

Quelle serait la meilleure approche?

Dans les exemples ci-dessus, je n'ai pas inclus les implémentations pour les méthodes andThen () et compose (). Si vous les ajoutez, vous devez ajouter 16 surcharges chacune: le TriFunc doit avoir un andthen () avec 16 arguments. Cela vous donnerait une erreur de compilation à cause de dépendances circulaires. De plus, vous n'auriez pas ces surcharges pour Function et BiFunction. Par conséquent, vous devez également définir Func avec un argument et Func avec deux arguments. Dans .NET, les dépendances circulaires seraient contournées en utilisant des méthodes d'extension qui ne sont pas présentes en Java.

7
Hans

Alternative est, ajoutez la dépendance ci-dessous,

<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr</artifactId>
    <version>0.9.0</version>
</dependency>

Maintenant, vous pouvez utiliser Vavr Function, comme ci-dessous jusqu'à 8 arguments,

3 arguments:

Function3<Integer, Integer, Integer, Integer> f = 
      (a, b, c) -> a + b + c;

5 arguments:

Function5<Integer, Integer, Integer, Integer, Integer, Integer> f = 
      (a, b, c, d, e) -> a + b + c + d + e;
5
Amol Damodar

J'ai trouvé le code source de BiFunction ici:

https://github.com/JetBrains/jdk8u_jdk/blob/master/src/share/classes/Java/util/function/BiFunction.Java

Je l'ai modifié pour créer TriFunction. Comme BiFunction, il utilise andThen () et pas compose (). Par conséquent, pour certaines applications nécessitant compose (), il se peut que cela ne soit pas approprié. Cela devrait aller pour des objets normaux. Un bon article sur andThen () et compos () peut être trouvé ici:

http://www.deadcoderising.com/2015-09-07-Java-8-functional-composition-using-compose-and-andthen/

import Java.util.Objects;
import Java.util.function.Function;

/**
 * Represents a function that accepts two arguments and produces a result.
 * This is the three-arity specialization of {@link Function}.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #apply(Object, Object)}.
 *
 * @param <S> the type of the first argument to the function
 * @param <T> the type of the second argument to the function
 * @param <U> the type of the third argument to the function
 * @param <R> the type of the result of the function
 *
 * @see Function
 * @since 1.8
 */
@FunctionalInterface
public interface TriFunction<S, T, U, R> {

    /**
     * Applies this function to the given arguments.
     *
     * @param s the first function argument
     * @param t the second function argument
     * @param u the third function argument
     * @return the function result
     */
    R apply(S s, T t, U u);

    /**
     * Returns a composed function that first applies this function to
     * its input, and then applies the {@code after} function to the result.
     * If evaluation of either function throws an exception, it is relayed to
     * the caller of the composed function.
     *
     * @param <V> the type of output of the {@code after} function, and of the
     *           composed function
     * @param after the function to apply after this function is applied
     * @return a composed function that first applies this function and then
     * applies the {@code after} function
     * @throws NullPointerException if after is null
     */
    default <V> TriFunction<S, T, U, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (S s, T t, U u) -> after.apply(apply(s, t, u));
    }
}
2
Vance

Vous pouvez également créer votre propre fonction en prenant les 3 paramètres

@FunctionalInterface
public interface MiddleInterface<F,T,V>{
    boolean isBetween(F from, T to, V middleValue);
}

MiddleInterface<Integer, Integer, Integer> middleInterface = 
(x,y,z) -> x>=y && y<=z; // true
0
Leandro Maro