Que signifie lambda avec 2 flèches dans Java 8?

118

J'ai déjà lu plusieurs tutoriels Java 8.

En ce moment, j'ai rencontré le sujet suivant: Java prend-il en charge Currying?

Ici, je vois le code suivant:

IntFunction<IntUnaryOperator> curriedAdd = a -> b -> a + b;
System.out.println(curriedAdd.apply(1).applyAsInt(12));

Je comprends que cet exemple somme 2 éléments mais je ne comprends pas la construction:

a -> b -> a + b;

Selon la partie gauche de l'expression, cette ligne doit implémenter la fonction suivante:

R apply(int value); 

Avant cela, je ne rencontrais que des lambdas avec une seule flèche.

gstackoverflow
la source
6
Comprenez-vous ce que signifie le curry? Cela fait partie intégrante de cette question.
BlueRaja - Danny Pflughoeft
12
C'est juste un lambda qui renvoie un lambda.
Ilmari Karonen

Réponses:

118

Si vous exprimez cela comme une syntaxe lambda non abrégée ou une syntaxe de classe anonyme Java pré-lambda, ce qui se passe est plus clair ...

La question originale. Pourquoi deux flèches? Simple, il y a deux fonctions en cours de définition ... La première fonction est une fonction de définition de fonction, la seconde est le résultat de cette fonction, qui se trouve également être une fonction. Chacun nécessite un ->opérateur pour le définir.

Non sténographie

IntFunction<IntUnaryOperator> curriedAdd = (a) -> {
    return (b) -> {
        return a + b;
    };
};

Pré-Lambda avant Java 8

IntFunction<IntUnaryOperator> curriedAdd = new IntFunction<IntUnaryOperator>() {
    @Override
    public IntUnaryOperator apply(final int value) {
        IntUnaryOperator op = new IntUnaryOperator() {
            @Override
            public int applyAsInt(int operand) {
                return operand + value;
            }
        };
        return op;
    }
};
Adam
la source
1
pré-lambda nécessite unfinal int value
njzk2
8
Oui, vous avez raison, mais j'ai écrit cela en utilisant toujours un compilateur Java 8 qui vous permet d'utiliser des choses qui sont "effectivement définitives"
Adam
1
@gstackoverflow oui, mais java 8 ne l'est paspre-lambda
njzk2
1
vous pouvez également utiliser le style pré-lambda dans java 8. Je l'ai écrit JFY
gstackoverflow
3
Les lambdas n'ont-ils pas été faits juste pour que les gens puissent marquer des points? :-)
Stephane
48

An IntFunction<R>est une fonction int -> R. An IntUnaryOperatorest une fonction int -> int.

Ainsi, an IntFunction<IntUnaryOperator>est une fonction qui prend un intcomme paramètre et renvoie une fonction qui prend un intcomme paramètre et renvoie un int.

a -> b -> a + b;
^    |         |
|     ---------
|         ^
|         |
|         The IntUnaryOperator (that takes an int, b) and return an int (the sum of a and b)
|
The parameter you give to the IntFunction

Il est peut-être plus clair si vous utilisez des classes anonymes pour "décomposer" le lambda:

IntFunction<IntUnaryOperator> add = new IntFunction<IntUnaryOperator>() {
    @Override
    public IntUnaryOperator apply(int a) {
        return new IntUnaryOperator() {
            @Override
            public int applyAsInt(int b) {
                return a + b;
            }
        };
    }
};
Alexis C.
la source
29

L'ajout de parenthèses peut rendre cela plus clair:

IntFunction<IntUnaryOperator> curriedAdd = a -> (b -> (a + b));

Ou probablement une variable intermédiaire peut aider:

IntFunction<IntUnaryOperator> curriedAdd = a -> {
    IntUnaryOperator op = b -> a + b;
    return op;
};
Tagir Valeev
la source
24

Réécrivons cette expression lambda avec des parenthèses pour la rendre plus claire:

IntFunction<IntUnaryOperator> curriedAdd = a -> (b -> (a + b));

Nous déclarons donc une fonction prenant an intqui renvoie a Function. Plus précisément, la fonction retournée prend an intet retourne an int(la somme des deux éléments): cela peut être représenté par un IntUnaryOperator.

Par conséquent, curriedAddune fonction prend-elle un intet retourne un IntUnaryOperator, elle peut donc être représentée par IntFunction<IntUnaryOperator>.

Tunaki
la source
9

Ce sont deux expressions lambda.

IntFunction<IntUnaryOperator> curriedAdd = 
  a -> { //this is for the fixed value
    return b -> { //this is for the add operation
      return a + b;
    };
  }

IntUnaryOperator addTwo = curriedAdd.apply(2);
System.out.println(addTwo.applyAsInt(12)); //prints 14
un meilleur oliver
la source
8

Si vous regardez, IntFunctioncela peut devenir plus clair: IntFunction<R>est un FunctionalInterface. Il représente une fonction qui prend un intet retourne une valeur de type R.

Dans ce cas, le type de retour Rest également a FunctionalInterface, à savoir un IntUnaryOperator. Ainsi, la première fonction (externe) elle-même renvoie une fonction.

Dans ce cas: Lorsqu'il est appliqué à un int, curriedAddest supposé retourner une fonction qui prend à nouveau an int(et retourne à nouveau int, car c'est ce qui IntUnaryOperatorfait).

En programmation fonctionnelle, il est courant d'écrire le type d'une fonction comme param -> return_valueet vous voyez exactement cela ici. Donc, le type de curriedAddest int -> int -> int(ou int -> (int -> int)si vous aimez mieux).

La syntaxe lambda de Java 8 va de pair avec cela. Pour définir une telle fonction, vous écrivez

a -> b -> a + b

qui est très similaire au calcul lambda réel:

λa λb a + b

λb a + best une fonction qui prend un seul paramètre bet renvoie une valeur (la somme). λa λb a + best une fonction qui accepte un seul paramètre aet renvoie une autre fonction d'un seul paramètre. λa λb a + brenvoie λb a + bavec aset à la valeur du paramètre.

dhke
la source