Что означает лямбда с двумя стрелками в Java 8?

118

Раньше я читал несколько руководств по Java 8.

Прямо сейчас я столкнулся со следующей темой: поддерживает ли Java каррирование?

Здесь я вижу следующий код:

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

Я понимаю, что этот пример суммирует 2 элемента, но я не могу понять конструкцию:

a -> b -> a + b;

Согласно левой части выражения, эта строка должна выполнять следующую функцию:

R apply(int value); 

До этого я встречал лямбды только с одной стрелкой.

gstackoverflow
источник
6
Вы понимаете, что такое карри? Это неотъемлемая часть этого вопроса.
BlueRaja - Дэнни Пфлугхофт
12
Это просто лямбда, которая возвращает лямбду.
Илмари Каронен

Ответы:

118

Если вы выразите это как не сокращенный синтаксис лямбда или синтаксис анонимного класса Java до лямбда, это станет более ясным, что происходит ...

Исходный вопрос. Почему две стрелки? Проще говоря, здесь определяются две функции ... Первая функция - это функция, определяющая функцию, вторая - результат этой функции, которая также является функцией. Каждый требует, чтобы ->оператор определил его.

Non-стенография

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

Pre-Lambda до 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;
    }
};
Адам
источник
1
pre-lambda требует afinal int value
njzk2
8
Да, вы правы, но я написал, что все еще использую компилятор Java 8, который позволяет вам использовать вещи, которые «фактически окончательны»
Адам
1
@gstackoverflow да, но тогда java 8 нетpre-lambda
njzk2
1
вы также можете использовать стиль pre-lambda в java 8. Я написал это в JFY
gstackoverflow
3
Разве лямбды не были сделаны только для того, чтобы люди могли набирать очки? :-)
Стефан
48

An IntFunction<R>- это функция int -> R. An IntUnaryOperator- это функция int -> int.

Таким образом, an IntFunction<IntUnaryOperator>- это функция, которая принимает intпараметр as и возвращает функцию, которая принимает intпараметр as и возвращает 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

Может быть, будет более понятно, если вы используете анонимные классы для «разложения» лямбда:

IntFunction<IntUnaryOperator> add = new IntFunction<IntUnaryOperator>() {
    @Override
    public IntUnaryOperator apply(int a) {
        return new IntUnaryOperator() {
            @Override
            public int applyAsInt(int b) {
                return a + b;
            }
        };
    }
};
Алексис С.
источник
29

Добавление скобок может прояснить это:

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

Или, возможно, может помочь промежуточная переменная:

IntFunction<IntUnaryOperator> curriedAdd = a -> {
    IntUnaryOperator op = b -> a + b;
    return op;
};
Тагир Валеев
источник
24

Давайте перепишем это лямбда-выражение в круглые скобки, чтобы было понятнее:

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

Итак, мы объявляем функцию, принимающую, intкоторая возвращает Function. В частности, возвращаемая функция принимает intи возвращает int(сумму двух элементов): это может быть представлено как IntUnaryOperator.

Следовательно, curriedAddэто функция, принимающая intи возвращающая IntUnaryOperator, поэтому ее можно представить как IntFunction<IntUnaryOperator>.

Tunaki
источник
9

Это два лямбда-выражения.

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
лучший оливер
источник
8

Если посмотреть, IntFunctionможет стать яснее: IntFunction<R>это файл FunctionalInterface. Он представляет функцию, которая принимает intи возвращает значение типа R.

В этом случае возвращаемый тип Rтакже является a FunctionalInterface, а именно IntUnaryOperator. Итак, первая (внешняя) функция сама возвращает функцию.

В этом случае: при применении к int, curriedAddпредполагается, что возвращает функцию, которая снова принимает int(и снова возвращает int, потому что это то, что IntUnaryOperatorделает).

В функциональном программировании принято писать тип функции как, param -> return_valueи вы видите именно это здесь. Так что тип curriedAddесть int -> int -> int(или, int -> (int -> int)если вам так больше нравится).

Лямбда-синтаксис Java 8 соответствует этому. Чтобы определить такую ​​функцию, вы пишете

a -> b -> a + b

что очень похоже на реальное лямбда-исчисление:

λa λb a + b

λb a + b- это функция, которая принимает один параметр bи возвращает значение (сумму). λa λb a + b- это функция, которая принимает один параметр aи возвращает другую функцию от одного параметра. λa λb a + bвозвращается λb a + bс aустановленным значением параметра.

dhke
источник