Co oznacza lambda z 2 strzałkami w Javie 8?

118

Przeczytałem wcześniej kilka samouczków dotyczących języka Java 8.

W tej chwili napotkałem następujący temat: Czy java obsługuje Currying?

Tutaj widzę następujący kod:

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

Rozumiem, że ten przykład sumuje 2 elementy, ale nie rozumiem konstrukcji:

a -> b -> a + b;

Zgodnie z lewą częścią wyrażenia, wiersz ten powinien implementować następującą funkcję:

R apply(int value); 

Wcześniej spotykałem lambdy tylko z jedną strzałką.

gstackoverflow
źródło
6
Czy rozumiesz, co oznacza curry? To całkiem integralna część tego pytania.
BlueRaja - Danny Pflughoeft
12
To po prostu lambda, która zwraca lambdę.
Ilmari Karonen

Odpowiedzi:

118

Jeśli wyrazisz to jako nieskrótową składnię lambda lub składnię anonimowych klas Java przed lambdą, będzie jaśniejsze, co się dzieje ...

Oryginalne pytanie. Dlaczego są dwie strzały? Prosto, definiowane są dwie funkcje ... Pierwsza funkcja jest funkcją definiującą funkcję, druga jest wynikiem tej funkcji, która również jest funkcją. Każdy z nich wymaga ->zdefiniowania go przez operatora.

Non-shorthand

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

Pre-Lambda przed 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
źródło
1
pre-lambda wymagafinal int value
njzk2
8
Tak, masz rację, ale napisałem, że wciąż używam kompilatora Java 8, który pozwala na używanie rzeczy, które są „ostatecznie ostateczne”
Adam
1
@gstackoverflow tak, ale java 8 niepre-lambda
njzk2
1
możesz też użyć stylu pre-lambda w java 8. Napisałem to JFY
gstackoverflow
3
Czy lambdy nie zostały zrobione tylko po to, aby ludzie TAK mogli zdobywać punkty? :-)
Stephane
48

Jest IntFunction<R>funkcją int -> R. Jest IntUnaryOperatorfunkcją int -> int.

Zatem an IntFunction<IntUnaryOperator>jest funkcją, która przyjmuje intparametr as i zwraca funkcję, która przyjmuje intparametr as i zwraca parametr 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

Może stanie się to bardziej zrozumiałe, jeśli użyjesz klas anonimowych do „dekompozycji” lambdy:

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.
źródło
29

Dodanie nawiasów może to wyjaśnić:

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

Lub prawdopodobnie zmienna pośrednia może pomóc:

IntFunction<IntUnaryOperator> curriedAdd = a -> {
    IntUnaryOperator op = b -> a + b;
    return op;
};
Tagir Valeev
źródło
24

Przepiszmy to wyrażenie lambda z nawiasami, aby było jaśniejsze:

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

Więc deklarujemy funkcję przyjmującą a, intktóra zwraca a Function. Mówiąc dokładniej, zwrócona funkcja przyjmuje an inti zwraca an int(sumę dwóch elementów): można to przedstawić jako IntUnaryOperator.

Dlatego curriedAddjest funkcją przyjmującą inti zwracającą an IntUnaryOperator, więc można ją przedstawić jako IntFunction<IntUnaryOperator>.

Tunaki
źródło
9

To dwa wyrażenia 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
lepszy oliver
źródło
8

Jeśli spojrzysz na IntFunctionto, może stać się jaśniejsze: IntFunction<R>jest FunctionalInterface. Reprezentuje funkcję, która przyjmuje an inti zwraca wartość typu R.

W tym przypadku typem zwracanym Rjest również a FunctionalInterface, a mianowicie an IntUnaryOperator. Zatem pierwsza (zewnętrzna) funkcja sama zwraca funkcję.

W tym przypadku: gdy stosowane do int, curriedAddma powrócić funkcję, która ponownie bierze int(i wraca ponownie int, ponieważ to, co IntUnaryOperatorrobi).

W programowaniu funkcyjnym typ funkcji często zapisuje się jako param -> return_valuei widać to dokładnie tutaj. Więc typ curriedAddjest int -> int -> int(lub int -> (int -> int)jeśli wolisz to).

Towarzyszy temu składnia lambda Java 8. Aby zdefiniować taką funkcję, piszesz

a -> b -> a + b

co jest bardzo podobne do rzeczywistego rachunku lambda:

λa λb a + b

λb a + bjest funkcją, która przyjmuje pojedynczy parametr bi zwraca wartość (sumę). λa λb a + bto funkcja, która akceptuje pojedynczy parametr ai zwraca inną funkcję pojedynczego parametru. λa λb a + bpowraca λb a + bz austawieniem na wartość parametru.

dhke
źródło