¿Qué significa lambda con 2 flechas en Java 8?

118

He leído varios tutoriales de Java 8 antes.

En este momento me encontré con el siguiente tema: ¿Java es compatible con Currying?

Aquí, veo el siguiente código:

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

Entiendo que este ejemplo suma 2 elementos pero no puedo entender la construcción:

a -> b -> a + b;

Según la parte izquierda de la expresión, esta fila debe implementar la siguiente función:

R apply(int value); 

Antes de esto, solo conocía lambdas con una sola flecha.

gstackoverflow
fuente
6
¿Entiendes lo que significa curry? Eso es bastante integral para esta pregunta.
BlueRaja - Danny Pflughoeft
12
Es solo una lambda que devuelve una lambda.
Ilmari Karonen

Respuestas:

118

Si expresa esto como sintaxis lambda no abreviada o sintaxis de clase anónima Java pre-lambda, es más claro lo que está sucediendo ...

La pregunta original. ¿Por qué hay dos flechas? Simple, se están definiendo dos funciones ... La primera función es una función que define una función, la segunda es el resultado de esa función, que también es función. Cada uno requiere un ->operador para definirlo.

No taquigrafía

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

Pre-Lambda antes de 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;
    }
};
Adán
fuente
1
pre-lambda requiere unfinal int value
njzk2
8
Sí, tienes razón, pero escribí que sigo usando un compilador de Java 8 que te permite usar cosas que son "efectivamente definitivas"
Adam
1
@gstackoverflow sí, pero java 8 no lo espre-lambda
njzk2
1
también puede usar el estilo pre-lambda en java 8. Lo escribí JFY
gstackoverflow
3
¿No se hicieron las lambdas solo para que la gente pudiera ganar puntos? :-)
Stephane
48

An IntFunction<R>es una función int -> R. An IntUnaryOperatores una función int -> int.

Por tanto, an IntFunction<IntUnaryOperator>es una función que toma an intcomo parámetro y devuelve una función que toma an intcomo parámetro y devuelve an 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

Tal vez sea más claro si usa clases anónimas para "descomponer" el 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.
fuente
29

Agregar paréntesis puede aclarar esto:

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

O probablemente la variable intermedia pueda ayudar:

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

Reescribamos esa expresión lambda entre paréntesis para que quede más claro:

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

Entonces estamos declarando una función tomando an intque devuelve a Function. Más específicamente, la función devuelta toma an inty devuelve an int(la suma de los dos elementos): esto se puede representar como an IntUnaryOperator.

Por lo tanto, curriedAddes una función que toma una inty devuelve una IntUnaryOperator, por lo que se puede representar como IntFunction<IntUnaryOperator>.

Atún
fuente
9

Son dos expresiones 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 mejor oliver
fuente
8

Si lo miras IntFunction, podría ser más claro: IntFunction<R>es un FunctionalInterface. Representa una función que toma inty devuelve un valor de tipo R.

En este caso, el tipo de retorno Rtambién es a FunctionalInterface, es decir, an IntUnaryOperator. Entonces, la primera función (externa) en sí misma devuelve una función.

En este caso: cuando se aplica a an int, curriedAddse supone que devuelve una función que vuelve a tomar an int(y vuelve de nuevo int, porque eso es lo que IntUnaryOperatorhace).

En la programación funcional, es común escribir el tipo de función como param -> return_valuey ve exactamente eso aquí. Entonces el tipo de curriedAddes int -> int -> int(o int -> (int -> int)si te gusta más).

La sintaxis lambda de Java 8 va de la mano con esto. Para definir tal función, escribe

a -> b -> a + b

que es muy similar al cálculo lambda real:

λa λb a + b

λb a + bes una función que toma un solo parámetro by devuelve un valor (la suma). λa λb a + bes una función que acepta un solo parámetro ay devuelve otra función de un solo parámetro. λa λb a + bvuelve λb a + bcon aestablecido en el valor del parámetro.

dhke
fuente