Функциональное программирование
Функциональное программирование было актуальным направлением в разработке программного обеспечения с самых ранних дней, но в современную эпоху приобрело новое значение. В этой статье рассматриваются концепции функционального программирования и предлагается практическое понимание с примерами на JavaScript и Java.
Определение функционального программирования
Функции являются основополагающими для организации кода; они существуют во всех языках программирования высокого порядка. Как правило, функциональное программирование означает использование функций с наилучшим эффектом для создания чистого и поддерживаемого программного обеспечения. Более конкретно, функциональное программирование - это набор подходов к кодированию, обычно описываемых как парадигма программирования.
Функциональное программирование иногда определяется в противовес объектно-ориентированному программированию (ООП) и процедурному программированию. Это вводит в заблуждение, поскольку эти подходы не являются взаимоисключающими, и большинство систем, как правило, используют все три.
Функциональное программирование в определенных случаях дает очевидные преимущества, оно широко используется во многих языках и фреймворках и занимает видное место в современных тенденциях программного обеспечения. Это полезный и мощный инструмент, который должен быть частью концептуального и синтаксического инструментария каждого разработчика.
Чистые функции
Идеалом в функциональном программировании является то, что известно как чистые функции. Чистая функция - это функция, результаты которой зависят только от входных параметров и работа которой не вызывает никаких побочных эффектов, то есть не оказывает никакого внешнего воздействия, кроме возвращаемого значения.
Красота чистой функции заключается в ее архитектурной простоте. Поскольку чистая функция сводится только к аргументам и возвращаемому значению (то есть к ее API), ее можно рассматривать как тупик сложности: ее единственное взаимодействие с внешней системой, в которой она работает, осуществляется через определенный API.
Это в отличие от ООП, где методы объекта предназначены для взаимодействия с состоянием объекта (элементами объекта), и в отличие от кода процедурного стиля, где внешним состоянием часто управляют изнутри функции.
Однако на практике функциям часто приходится взаимодействовать с более широким контекстом, о чем свидетельствует хук Useffect React.
Неизменность
Еще один принцип философии функционального программирования - не изменять данные вне функции. На практике это означает, что следует избегать изменения входных аргументов функции. Вместо этого возвращаемое значение функции должно отражать проделанную работу. Это способ избежать побочных эффектов. Это облегчает рассуждение о влиянии функции, поскольку она работает в рамках более крупной системы.
Функции первого класса
Помимо идеала чистой функции, в реальной практике программирования функциональное программирование зависит от функций первого класса. Функция первого класса - это функция, которая рассматривается как “вещь в себе”, способная стоять отдельно и обрабатываться независимо. Функциональное программирование стремится использовать преимущества языковой поддержки при использовании функций в качестве переменных, аргументов и возвращаемых значений для создания элегантного кода.
Поскольку функции первого класса настолько гибки и полезны, даже такие сильные языки ООП, как Java и C#, перешли на поддержку функций первого класса. Это является стимулом для поддержки лямбда-выражений в Java 8.
Другой способ описания функций первого класса - это функции как данные. Другими словами, функция первого класса может быть назначена переменной, как и любым другим данным. Когда вы пишете let myFunc = function () {}, вы используете функцию в качестве данных.
Функции более высокого порядка
Функция, которая принимает функцию в качестве аргумента или возвращает функцию, известна как функция более высокого порядка-функция, которая оперирует функцией. Как JavaScipt, так и Java в последние годы добавили улучшенный синтаксис функций. Java добавила оператор стрелки и оператор двойного двоеточия. В JavaScript добавлен оператор стрелки. Эти операторы предназначены для упрощения определения и использования функций, особенно встроенных в качестве анонимных функций. Анонимная функция - это функция, которая определяется и используется без указания ссылочной переменной.
Пример функционального программирования: Коллекции
Возможно, самый яркий пример того, где блещет функциональное программирование, - это работа с коллекциями. Это связано с тем, что возможность применять фрагменты функциональности к элементам коллекции естественным образом соответствует идее чистой функции. Рассмотрим код, в котором используется функция JavaScript map() для заглавных букв в массиве.
let letters = ["a", "b", "c"]; console.info( letters.map((x) => x.toUpperCase()) ); // outputs ["A", "B", "C"]Использование map() и анонимной функции в JavaScript
Прелесть этого синтаксиса в том, что код жестко сфокусирован. Никаких обязательных процедур, таких как цикл и манипуляция массивом, не требуется. Мыслительный процесс того, что делается, четко выражен в этом коде.
То же самое достигается с помощью оператора стрелки Java, как показано в коде ниже.
import java.util.*; import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; //... List lower = Arrays.asList("a","b","c"); System.out.println(lower.stream().map(s -> s.toUpperCase()).collect(toList())); // outputs ["A", "B", "C"]Использование map() и анонимной функции в Java
В коде выше используется потоковая библиотека Java 8 для выполнения той же задачи, что и в верхнем регистре списка букв. Обратите внимание, что синтаксис основного оператора стрелки практически идентичен JavaScript, и они делают то же самое, т. е. создают функцию, которая принимает аргументы, выполняет логику и возвращает значение. (Важно отметить, что если в теле функции, определенной таким образом, отсутствуют фигурные скобки, то возвращаемое значение задается автоматически.)
Продолжая работу с Java, рассмотрим оператор двойного двоеточия. Этот оператор позволяет ссылаться на метод в классе: в данном случае метод в верхнем регистре в строковом классе. Тут делается то же самое, что и в коде выше. Различные синтаксисы пригодятся для разных сценариев.
// ... List upper = lower.stream().map(String::toUpperCase).collect(toList());Оператор двойного двоеточия Java
Во всех трех приведенных выше примерах вы можете видеть, что работают функции более высокого порядка. Функция map() на обоих языках принимает функцию в качестве аргумента.
Иными словами, вы могли бы рассматривать передачу функций в другие функции (в API массива или иным образом) как функциональные интерфейсы. Функции поставщика (которые используют функции параметров) являются подключаемыми модулями для обобщенной логики.
Это очень похоже на шаблон стратегии в ООП (и действительно, в Java интерфейс с одним методом создается под капотом), но компактность функции создает очень жесткий протокол компонентов.
В качестве другого примера рассмотрим код, в котором определен обработчик маршрута в среде Express для Node.js.
var express = require('express'); var app = express(); app.get('/', function (req, res) { res.send('One Love!'); });Функциональный обработчик маршрута в Express
Код выше является прекрасным примером функционального программирования в том смысле, что он позволяет четко определить, что именно требуется для отображения маршрута и обработки запросов и ответов, хотя можно утверждать, что манипулирование объектом ответа в теле функции является побочным эффектом.
Функции Карри
Теперь рассмотрим понятие функционального программирования функций, возвращающих функции. Это встречается реже, чем функции в качестве аргументов. В листинге ниже приведен пример из общего шаблона реакции, в котором синтаксис жирной стрелки связан цепочкой.
handleChange = field => e => { e.preventDefault(); // Обработать событие }Функция карри в React
Цель вышеизложенного состоит в том, чтобы создать обработчик событий, который примет рассматриваемое поле, а затем событие. Это полезно, потому что вы можете применить одно и то же изменение к нескольким полям. Короче говоря, один и тот же обработчик можно использовать для нескольких полей.
В коде выше приведен пример функции карри. “Функция Карри” - это немного неприятное название. Это делает честь человеку, что приятно, но не описывает концепцию, что сбивает с толку. В любом случае идея заключается в том, что, когда у вас есть функции, возвращающие функции, вы можете связывать их вызовы более гибким способом, чем создание одной функции с несколькими аргументами.
При вызове функций такого рода вы столкнетесь с характерным синтаксисом “цепных скобок”: изменение(поле)(событие).
Программирование в большом
Предыдущие примеры предлагают практическое понимание функционального программирования в узком контексте, но функциональное программирование предназначено для того, чтобы принести больше пользы программированию в целом. Иными словами, функциональное программирование предназначено для создания более чистых, более устойчивых крупномасштабных систем.
Трудно привести примеры этого, но одним из примеров в реальном мире является шаг React по продвижению функциональных компонентов. Команда React отметила, что более лаконичный функциональный стиль компонента обеспечивает преимущества, которые усиливаются по мере расширения архитектуры интерфейса.
Еще одна система, которая в значительной степени использует функциональное программирование, - это ReactiveX. Крупномасштабные системы, построенные на потоках событий, которые использует ReactiveX, могут извлечь выгоду из несвязанного взаимодействия программных компонентов. Angular полностью использует ReactiveX (RxJS) по всем направлениям в качестве подтверждения этой способности.
Переменная области и контекст
Наконец, проблема, которая не обязательно является частью функционального программирования как парадигмы, но на которую очень важно обращать внимание при выполнении функционального программирования, - это проблема переменной области и контекста.
В JavaScript контекст конкретно означает, к чему относится ключевое слово this. В случае оператора стрелки JavaScript это относится к включающему контексту. Функция, определенная с использованием традиционного синтаксиса, получает свой собственный контекст. Обработчики событий в объектах DOM могут воспользоваться этим фактом, чтобы убедиться, что ключевое слово this относится к обрабатываемому элементу.
Область действия относится к горизонту переменных, то есть к тому, какие переменные видны. В случае всех функций JavaScript (как с жирной стрелкой, так и традиционных), а также в случае анонимных функций Java, определяемых стрелкой, область действия является областью действия заключающего тела функции, хотя в Java доступны только те переменные, которые являются фактически окончательными. Вот почему такие функции называются закрытиями. Этот термин означает, что функция заключена в ее содержащую область.
Это важно помнить: такие анонимные функции имеют полный доступ к переменным в области видимости. Внутренняя функция может работать с переменными внешней функции. Это можно считать побочным эффектом не чистой функции.
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.
Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.