На этом шаге мы рассмотрим преимущества функционального стиля программирования.
Посмотрим на пример использования zip() в примере из предыдущего шага. Представьте реализацию этого же задания в объектно-ориентированном стиле или, говоря шире, - в императивном стиле. В Java, например, решение этой задачи могло бы выглядеть примерно так:
List<String> employees = Arrays.asList("Denny", "Claudette", "Peter"); List<String> shirtSizes = Arrays.asList("large", "x-large", "medium"); Map<String, String> employeeShirtSizes = new HashMap<>(); for (int i = 0; i < employees.size; i++) { employeeShirtSizes.put(employees.get(i), shirtSizes.get(i)); }
На первый взгляд императивная версия решает эту задачу с тем же качеством и количеством строк кода, что и функциональная версия в примере из предыдущего шага. Но при более внимательном рассмотрении можно заметить, что функциональный подход предлагает ряд важных преимуществ:
Первые два пункта - это явные преимущества, так как новые операции в императивном стиле обычно требуют создания большего количества переменных для хранения состояния. Например, коллекция employeeShirtSize должна объявляться вне цикла for, чтобы потом можно было использовать результаты вычислений.
Этот шаблон требует вручную добавлять результаты в employeeShirtSize в каждом цикле. Если вы забудете добавить значения в коллекцию employeeShirtSize (этот шаг легко упустить из виду), остальная часть программы может работать некорректно. Каждый дополнительный шаг увеличивает вероятность появления подобной ошибки.
Функциональная реализация, напротив, неявно накапливает новую коллекцию после каждой операции в цепочке, не требуя объявления новых переменных:
val formattedSwagOrders = employees.zip(shirtSize).toMap()
Так как в функциональном стиле значения накапливаются в новой коллекции неявно, как часть работы функциональной, остается меньше места для ошибок.
Теперь поговорим о третьем преимуществе, о котором речь шла чуть раньше. Так как все функциональные операции предназначены для работы с итерируемыми коллекциями, добавление еще одного шага в функциональную цепочку является тривиальной задачей. Например, предположим, что после формирования массива employeeShirtSize нужно составить список заказов в определенном формате. В императивном стиле для этого пришлось бы добавить следующий код:
List<String> formattedSwagOrders = new ArrayList<>(); for (Map.Entry<String, String> shirtSize : employeeShirtSizes.entrySet()) { formattedSwagOrders.add(String.format("%s, shirt size: %s", it.getKey(), it.getValue()); }
Добавление новой коллекции и нового цикла for, наполняющего коллекцию данными, привело к тому, что появилось больше сущностей, больше состояний, за которыми надо следить.
В функциональном стиле дальнейшие операции легко добавляются в цепочку и не нуждаются в дополнительном состоянии. В функциональном стиле то же самое можно реализовать простым добавлением:
.map { "${it.key}, shirt size: ${it.value}" }
На следующем шаге мы рассмотрим последовательности.