O que lambda com 2 setas significa em Java 8?

118

Eu li vários tutoriais de Java 8 antes.

Agora eu encontrei o seguinte tópico: Java oferece suporte ao Currying?

Aqui, vejo o seguinte código:

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

Eu entendo que este exemplo soma 2 elementos, mas não consigo entender a construção:

a -> b -> a + b;

De acordo com a parte esquerda da expressão, esta linha deve implementar a seguinte função:

R apply(int value); 

Antes, eu só conhecia lambdas com uma flecha.

gstackoverflow
fonte
6
Você entende o que significa currying? Isso é parte integrante desta questão.
BlueRaja - Danny Pflughoeft
12
É apenas um lambda que retorna um lambda.
Ilmari Karonen

Respostas:

118

Se você expressar isso como sintaxe lambda não abreviada ou sintaxe de classe anônima Java pré-lambda, ficará mais claro o que está acontecendo ...

A pergunta original. Por que são duas setas? Simples, há duas funções sendo definidas ... A primeira função é uma função definidora de função, a segunda é o resultado dessa função, que também passa a ser função. Cada um requer um ->operador para defini-lo.

Não taquigrafado

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

Pré-Lambda antes do 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ão
fonte
1
pré-lambda requer umfinal int value
njzk2
8
Sim, você está correto, mas eu escrevi que ainda estou usando um compilador Java 8 que permite que você use coisas que são "efetivamente finais"
Adam,
1
@gstackoverflow sim, mas java 8 não épre-lambda
njzk2
1
você pode usar o estilo pré-lambda em java 8 também. Eu escrevi JFY
gstackoverflow
3
Lambdas não foram feitas apenas para que as pessoas pudessem marcar pontos? :-)
Stephane
48

Um IntFunction<R>é uma função int -> R. Um IntUnaryOperatoré uma função int -> int.

Assim, um IntFunction<IntUnaryOperator>é uma função que leva um intcomo parâmetro e retornar uma função que leva um intcomo parâmetro e retornar um 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

Talvez seja mais claro se você usar classes anônimas para "decompor" o 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.
fonte
29

Adicionar parênteses pode tornar isso mais claro:

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

Ou provavelmente a variável intermediária pode ajudar:

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

Vamos reescrever essa expressão lambda com parênteses para torná-la mais clara:

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

Portanto, estamos declarando uma função que recebe um intque retorna a Function. Mais especificamente, a função retornada recebe um inte retorna um int(a soma dos dois elementos): isso pode ser representado como um IntUnaryOperator.

Portanto, curriedAddé uma função que recebe um inte retorna um IntUnaryOperator, portanto, pode ser representada como IntFunction<IntUnaryOperator>.

Tunaki
fonte
9

São duas expressões 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
um oliver melhor
fonte
8

Se você olhar IntFunction, pode ficar mais claro: IntFunction<R>é a FunctionalInterface. Ele representa uma função que recebe um inte retorna um valor do tipoR .

Nesse caso, o tipo de retorno Rtambém é a FunctionalInterface, ou seja, um IntUnaryOperator. Então o primeiro função (externa) retorna uma função.

Neste caso: Quando aplicado a um int, curriedAdddeve retornar uma função que novamente recebe um int(e retorna novamente int, porque é isso que IntUnaryOperatorfaz).

Na programação funcional, é comum escrever o tipo de uma função como param -> return_valuee você vê exatamente isso aqui. Portanto, o tipo de curriedAddé int -> int -> int(ou int -> (int -> int)se preferir, melhor).

A sintaxe lambda do Java 8 vai junto com isso. Para definir tal função, você escreve

a -> b -> a + b

que é muito semelhante ao cálculo lambda real:

λa λb a + b

λb a + bé uma função que recebe um único parâmetro be retorna um valor (a soma). λa λb a + bé uma função que aceita um único parâmetro ae retorna outra função de um único parâmetro. λa λb a + bretorna λb a + bcom adefinido para o valor do parâmetro.

dhke
fonte