5 votes

Java 8 Programmation de style de fonction Quelle est la différence entre le curry et la composition de fonctions

Je suis assez nouveau dans le monde de la programmation fonctionnelle. J'essaie le nouveau style de programmation fonctionnelle introduit avec Java 8. Récemment, j'ai découvert le currying et la composition de méthode. Comprendre la véritable essence de la programmation fonctionnelle en utilisant Java est assez difficile et j'ai maintenant quelques questions, Cependant, avant de poser toutes ces questions, j'ai essayé la même chose en Python et maintenant je suis un peu familier avec quelques concepts de base.

1. En Java, en quoi le Currying et la composition de méthode sont-ils différents ? En fait, je ne vois aucune différence du tout, surtout après avoir lu cet article https://dzone.com/articles/higher-order-functions

2. En tant que programmeur (de mon point de vue en programmation Java), pourquoi préférerais-je le currying? Par exemple, pourquoi ferais-je cela f(x){ return g(y) } au lieu de f(x,y){ return x(y)}, quelle différence cela fait-il ?

8voto

biziclop Points 21446

Alors que les deux opérations produisent une fonction, l'exemple clarifie assez bien la différence :

  1. Le curryfication prend une seule fonction f() et produit une fonction "intermédiaire" f'() qui est la même que f() mais avec certains paramètres déjà fixés. Lorsque vous remplirez le reste des paramètres, vous évaluerez la fonction originale f().
  2. Alors que la composition prend deux fonctions f() et g() et crée une fonction complètement différente g(f()).

Prenons un exemple simple : f(x,y) = x+y, où x et y sont des entiers. Aucun nombre et combinaison de curryfication de cette fonction ne peut aboutir à une fonction qui renverra un résultat non entier. Mais si on la compose avec g(x) = x/2, on obtient g(f(x,y)) = (x+y)/2, qui renverra bien sûr des valeurs non entières.

Pourquoi utiliser alors la curryfication ?

Les méthodes d'instance Java, par exemple, sont le résultat d'un processus assez similaire. Les méthodes d'instance diffèrent des méthodes statiques en ce qu'elles ont un paramètre caché supplémentaire appelé this. Lorsque vous dites new Foo(), vous liez essentiellement ce paramètre caché au nouvel objet Foo créé. Ainsi, au lieu d'appeler la fonction void bar(Foo this, int x), vous pouvez simplement la référencer comme void bar(int x), avec le premier paramètre déjà fixé en place. (Au fait, void bar(Foo this, int x) est en fait une syntaxe Java parfaitement valide, mais nous ne l'utilisons presque jamais.)

Ce n'est pas tout à fait une coïncidence, car les langages fonctionnels purs ne peuvent avoir que des fonctions dont les sorties dépendent uniquement de leurs entrées (contrairement aux langages orientés objet, où la sortie d'une méthode peut également dépendre de l'état interne de l'objet auquel la méthode appartient.)

En guise de conseil général, si vous souhaitez apprendre l'essence de la programmation fonctionnelle, il est préférable de ne pas le faire à partir de Java. Pas même de Scala. Essayez d'apprendre à partir d'un langage fonctionnel pur comme Haskell, puis vous pourrez revenir à Java et comprendre beaucoup mieux quel sous-ensemble de la PF a été implémenté et comment.

6voto

fg78nc Points 2371

Je voudrais ajouter du code à une très belle explication de @biziclop :

Exemple de curryfication en Java fonctionnel :

BiFunction> currying = (x, y) -> z -> x * y / z;
    System.out.println(currying.apply(5, 6).apply(2)); // 15

Comme vous pouvez le constater, la lambda est paramétrée. Dans cet exemple, nous effectuons une curryfication pour multiplier 5 par 6, puis diviser par 2.

Tout d'abord, apply(5) est invoqué et la variable x prend la valeur 5 et la fonction devient 5 * y / z

Ensuite, apply(6) est invoqué et la variable 'y' prend la valeur '6' et la fonction devient 5 * 6 / z

Ensuite, apply(2) est invoqué et la variable 'z' prend la valeur '2' et la fonction devient 5 * 6 / 2

Comme vous pouvez le voir, la curryfication de cette manière est peu utile en Java. La curryfication est utile dans les langages fonctionnels purs, où les fonctions sont limitées à un seul argument et elles bénéficient de la curryfication, qui transforme une fonction prenant plusieurs arguments, de sorte qu'elle puisse être appelée plusieurs fois avec chaque invocation d'un seul argument.

Alors, comment pouvez-vous bénéficier de la curryfication en Java ?

Cela est utile lorsque vous devez paramétrer une fonction à plusieurs niveaux. Par exemple, disons que nous avons plusieurs collections, représentant différentes catégories, et que nous voulons récupérer des éléments particuliers de chaque catégorie. Voici un exemple simple, donné deux collections, représentant des nombres écrits, catégorisés comme ones et tens. Exemple :

public class Currying {

    private static List ones = 
           Arrays.asList("Zero", "One", "Two", "Three", "Four", 
                                 "Five", "Six", "Seven", "Eight", "Nine");
    private static List tens =
           Arrays.asList("Zero", "Ten", "Twenty", "Thirty", "Forty",
                                "Fifty", "Sixty", "Seventy", "Eighty", "Ninety");

    public static Function> getNumbers() {
        return units -> number -> {
                        return units == "Ones" ? ones.get(number % 10) 
                                               : tens.get(number % 10);
                                  };
    }

    public static void main(String[] args) {
        Function> currying = getNumbers();
        System.out.println(currying.apply("Tens").apply(8)); // 80
        System.out.println(currying.apply("Ones").apply(2)); // 2
    }

}

Dans l'exemple ci-dessus, la fonction currying renvoie une autre fonction currying.apply("Ones").apply(2));

Tout d'abord, apply("Tens") est invoqué et la variable units devient Tens

Ensuite, apply(2) est invoqué et la variable number devient 8 en récupérant 80 de la collection tens.

La même logique s'applique à currying.apply("Ones").apply(2)).

4voto

Currying est un moyen de créer de nouvelles fonctions en "intégrant" des arguments dans des fonctions existantes. Cela se fait généralement dans des langages comme Haskell où la syntaxe du langage se prête facilement à le faire.

Un exemple typique est d'avoir une fonction (addTwoNumbers a b) qui ajoute deux nombres où le curryage consiste à fournir moins d'arguments pour obtenir une fonction qui prend les arguments restants pour effectuer des actions. Par exemple, (addTwoNumbers 42) où a est fourni (42) mais pas b, est une fonction (non un résultat) qui prend un argument (b) et renvoie 42+b. Ainsi, ((addTwoNumbers 42) 10) renverrait 52.

Comme vous pouvez le voir, la syntaxe du langage doit aider pour que cela fonctionne bien, et Java n'aide pas beaucoup, c'est pourquoi cela n'apparaît pas souvent dans les tutoriels. Les aspects fonctionnels de Java 8 consistent principalement à éviter les boucles for dans le code en utilisant Streams et à avoir un nombre raisonnable de fonctions prédéfinies à utiliser comme échafaudage avec des expressions lambda. Ils ont une évaluation paresseuse dans Streams, ce qui est très agréable et constitue une grande réalisation, mais n'apporte pas beaucoup d'expressivité dans le code au programmeur.

Voir https://wiki.haskell.org/Currying pour une explication plus technique.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X