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

Определение функционального программирования

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

Функциональное программирование иногда определяется как противопоставление объектно-ориентированному программированию (ООП) и процедурному программированию. Это вводит в заблуждение, поскольку эти подходы не исключают друг друга, и в большинстве систем, как правило, используются все три.

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

Чистые функции

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

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

Это контрастирует с ООП, где методы объекта предназначены для взаимодействия с состоянием объекта (членами объекта), и в отличие от кода процедурного стиля, где внешнее состояние часто управляется изнутри функции.

Однако на практике функциям часто приходится взаимодействовать с более широким контекстом, о чем свидетельствует React’s useEffect крюк.

Неизменность

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

Функции первого класса

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

Поскольку функции первого класса настолько гибки и полезны, даже языки с сильным ООП, такие как Java и C #, перешли на поддержку функций первого класса. Это стало стимулом для поддержки лямбда-выражений в Java 8.

Другой способ описания функций первого класса – функции как данные. То есть функция первого класса может быть назначена переменной, как и любые другие данные. Когда ты пишешь let myFunc = function(){} вы используете функцию как данные.

Функции высшего порядка

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

И JavaScipt, и Java в последние годы добавили улучшенный синтаксис функций. В Java добавлены оператор стрелки и оператор двойного двоеточия. JavaScript добавил оператор стрелки. Эти операторы предназначены для упрощения определения и использования функций, особенно встроенных в анонимные функции. Анонимная функция – это функция, которая определяется и используется без указания ссылочной переменной.

Пример функционального программирования: Коллекции

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

Рассмотрим листинг 1, в котором используется JavaScript. map() функция для прописных букв в массиве.

Листинг 1. Использование map () и анонимной функции в JavaScript.

let letters = ["a", "b", "c"];
console.info( letters.map((x) => x.toUpperCase()) ); // outputs ["A", "B", "C"]

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

То же самое достигается с помощью оператора стрелки Java, как показано в листинге 2.

Листинг 2. Использование map () и анонимной функции в 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"]

В листинге 2 потоковая библиотека Java 8 используется для выполнения той же задачи – верхнего регистра списка букв. Обратите внимание, что синтаксис основного оператора стрелки практически идентичен синтаксису JavaScript, и они делают то же самое, т. Е. Создают функцию, которая принимает аргументы, выполняет логику и возвращает значение. (Важно отметить, что если в теле функции, определенном таким образом, отсутствуют фигурные скобки, то автоматически возвращается возвращаемое значение.)

Продолжая работу с Java, рассмотрим оператор двойного двоеточия в листинге 3. Этот оператор позволяет вам ссылаться на метод класса: в этом случае toUpperCase в классе String. В листинге 3 выполняется то же самое, что и в листинге 2. Различные синтаксисы пригодятся для разных сценариев.

Листинг 3. Оператор двойного двоеточия Java

// ...
List upper = lower.stream().map(String::toUpperCase).collect(toList());

Во всех трех приведенных выше примерах вы можете видеть, что работают функции высшего порядка. В map() Функция на обоих языках принимает функцию в качестве аргумента.

Другими словами, вы можете рассматривать передачу функций в другие функции (в Array API или иначе) как на функциональные интерфейсы. Функции провайдера (которые используют функции параметров) являются надстройками для обобщенной логики.

Это очень похоже на шаблон стратегии в ООП (и действительно, в Java интерфейс с одним методом генерируется под капотом), но компактность функции делает протокол компонентов очень жестким.

В качестве другого примера рассмотрим листинг 4, который определяет обработчик маршрута в платформе Express для Node.js.

Листинг 4. Функциональный обработчик маршрута в Express.

var express = require('express');
var app = express();
app.get("https://www.infoworld.com/", function (req, res) {
  res.send('One Love!');
});

Листинг 4 является прекрасным примером функционального программирования, поскольку он позволяет четко определить, что именно требуется для отображения маршрута и обработки запросов и ответов – хотя можно утверждать, что манипулирование объектом ответа в теле функции является побочным эффектом. .

Карри-функции

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

Листинг 5. Каррированная функция в React

handleChange = field => e => {
e.preventDefault();
// Handle event
}

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

В листинге 5 приведен пример функция карри. «Карри-функция» – немного неприятное название. Он чтит человека, что приятно, но не описывает концепцию, что сбивает с толку. В любом случае идея состоит в том, что, когда у вас есть функции, возвращающие функции, вы можете объединить их вызовы в цепочку более гибким способом, чем создание одной функции с несколькими аргументами.

При вызове таких функций вы столкнетесь с характерным синтаксисом «связанных скобок»: handleChange(field)(event).

Программирование в целом

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

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

Еще одна система, интенсивно использующая функциональное программирование, – это ReactiveX. Крупномасштабные системы, построенные на виде потоков событий, которые использует ReactiveX, могут получить выгоду от независимого взаимодействия компонентов программного обеспечения. Angular полностью принимает ReactiveX (RxJS) во всем, как признание этой мощи.

Объем и контекст переменной

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

В JavaScript контекст конкретно означает, что this ключевое слово разрешается в. В случае оператора стрелки JavaScript, this относится к охватывающему контексту. Функция, определенная с использованием традиционного синтаксиса, получает свой собственный контекст. Обработчики событий на объектах DOM могут воспользоваться этим фактом, чтобы гарантировать, что this ключевое слово относится к обрабатываемому элементу.

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

Это важно помнить: такие анонимные функции имеют полный доступ к переменным в области видимости. Внутренняя функция может работать с переменными внешней функции. Это можно рассматривать как побочный эффект не чистой функции.

Авторские права © 2021 IDG Communications, Inc.


#Что #такое #функциональное #программирование #Практическое #руководство

Source link