Рубрики
Без рубрики

Карриинг на Яве

Узнайте, как улучшить функциональное программирование с помощью цепочек вызовов одной параметрной функции. Карриинг превращает несколько параметров в беглый стиль.

Автор оригинала: Jan Hauer.

1. Введение

Начиная с Java 8, мы можем определить одно- и двух параметрные функции в Java, что позволяет нам вводить их поведение в другие функции, передавая их в качестве параметров. Но для функций с большими параметрами, мы полагаемся на внешние библиотеки, как Vavr .

Другой вариант заключается в использовании карри . Комбинируя карри и функциональные интерфейсы , мы можем даже определить легко читаемых строителей, которые заставляют пользователя предоставлять все входные данные.

В этом учебнике мы определим карри и представляем его использование .

2. Простой пример

Рассмотрим конкретный пример письма с несколькими параметрами.

Наша упрощенная первая версия требует только тела и приветствия:

class Letter {
    private String salutation;
    private String body;
    
    Letter(String salutation, String body){
        this.salutation = salutation;
        this.body = body;
    }
}

2.1. Создание методом

Такой объект можно легко создать методом:

Letter createLetter(String salutation, String body){
    return new Letter(salutation, body);
}

2.2. Создание с biFunction

Вышеупомянутый метод работает просто отлично, но мы, возможно, потребуется поставить такое поведение на что-то написано в функциональном стиле. Начиная с Java 8, мы можем использовать БиФункционная для этой цели:

BiFunction SIMPLE_LETTER_CREATOR 
  = (salutation, body) -> new Letter(salutation, body);

2.3. Создание с последовательностью функций

Мы также можем сделать это как последовательность функций каждый с одним параметром:

Function> SIMPLE_CURRIED_LETTER_CREATOR 
  = salutation -> body -> new Letter(salutation, body);

Мы видим, что приветствие карты к функции. Полученная функция отображается на новом Письмо объект. Узнайте, как изменился тип возврата по БиФункционная . Мы используем только Функциональные класса. Такое преобразование в последовательность функций называется карри.

3. Расширенный пример

Для того, чтобы показать преимущества карри, давайте расширим наши Письмо конструктор класса с большими параметрами:

class Letter {
    private String returningAddress;
    private String insideAddress;
    private LocalDate dateOfLetter;
    private String salutation;
    private String body;
    private String closing;

    Letter(String returningAddress, String insideAddress, LocalDate dateOfLetter, 
      String salutation, String body, String closing) {
        this.returningAddress = returningAddress;
        this.insideAddress = insideAddress;
        this.dateOfLetter = dateOfLetter;
        this.salutation = salutation;
        this.body = body;
        this.closing = closing;
    }
}

3.1. Создание методом

Как и раньше, мы можем создавать объекты методом:

Letter createLetter(String returnAddress, String insideAddress, LocalDate dateOfLetter, 
  String salutation, String body, String closing) {
    return new Letter(returnAddress, insideAddress, dateOfLetter, salutation, body, closing);
}

3.2. Функции для произвольного арбитража

Arity – это мера количества параметров, которые принимает функция. Java предоставляет существующие функциональные интерфейсы для нулевой ( Поставщик ), неайные ( Функциональные ), и двоичные ( БиФункционная ), но это все. Без определения нового функционального интерфейса мы не можем предоставить функцию с шестью входными параметрами.

Карри – наш выход. Он превращает произвольное арки в последовательность унитарных функций . Так что для нашего примера, мы получаем:

Function>>>>> LETTER_CREATOR =
  returnAddress
    -> closing
    -> dateOfLetter
    -> insideAddress
    -> salutation
    -> body
    -> new Letter(returnAddress, insideAddress, dateOfLetter, salutation, body, closing);

3.3. Многословный тип

Очевидно, что вышеуказанный тип не совсем читаемым. С этой формой мы используем “применить” шесть раз, чтобы создать Письмо :

LETTER_CREATOR
  .apply(RETURNING_ADDRESS)
  .apply(CLOSING)
  .apply(DATE_OF_LETTER)
  .apply(INSIDE_ADDRESS)
  .apply(SALUTATION)
  .apply(BODY);

3.4. Предварительные значения заполнения

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

Function>>>> 
  LETTER_CREATOR_PREFILLED = returningAddress -> LETTER_CREATOR.apply(returningAddress).apply(CLOSING);

Обратите внимание, что для того, чтобы это было полезно, мы должны тщательно выбрать порядок параметров в исходной функции, чтобы менее конкретными были первые.

4. Шаблон строителя

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

AddReturnAddress builder(){
    return returnAddress
      -> closing
      -> dateOfLetter
      -> insideAddress
      -> salutation
      -> body
      -> new Letter(returnAddress, insideAddress, dateOfLetter, salutation, body, closing);
}

Вместо последовательности функций мы используем последовательность функциональных интерфейсов . Обратите внимание, что возвращающийся тип вышеуказанного определения AddReturnAddress . В следующем мы должны только определить промежуточные интерфейсы:

interface AddReturnAddress {
    Letter.AddClosing withReturnAddress(String returnAddress);
}
    
interface AddClosing {
    Letter.AddDateOfLetter withClosing(String closing);
}
    
interface AddDateOfLetter {
    Letter.AddInsideAddress withDateOfLetter(LocalDate dateOfLetter);
}

interface AddInsideAddress {
    Letter.AddSalutation withInsideAddress(String insideAddress);
}

interface AddSalutation {
    Letter.AddBody withSalutation(String salutation);
}

interface AddBody {
    Letter withBody(String body);
}

Таким образом, используя это для создания Письмо вполне объяснимо:

Letter.builder()
  .withReturnAddress(RETURNING_ADDRESS)
  .withClosing(CLOSING)
  .withDateOfLetter(DATE_OF_LETTER)
  .withInsideAddress(INSIDE_ADDRESS)
  .withSalutation(SALUTATION)
  .withBody(BODY));

Как и прежде, мы можем предварительно заполнить объект письма:

AddDateOfLetter prefilledLetter = Letter.builder().
  withReturnAddress(RETURNING_ADDRESS).withClosing(CLOSING);

Обратите внимание, интерфейсы обеспечивают порядок заполнения . Таким образом, мы не можем просто предварительно заполнить закрытие .

5. Заключение

Мы видели, как применять карри, поэтому мы не ограничены ограниченным числом параметров, поддерживаемых стандартными функциональными интерфейсами Java. Кроме того, мы можем легко предварительно заполнить первые несколько параметров. Кроме того, мы узнали, как использовать это для создания читаемого строителя.

Как всегда, полные образцы кода доступны на GitHub .