Что такое функциональное программирование

Функциональная парадигма программирования (functional programming) — это способ описания программы из отдельных блоков кода (функций), которые не влияют на глобальный контекст всей программы. В функциональных языках компилятор сам определяет последовательность выполнения программных блоков или участков кода. Задача программиста описать взаимодействие между данными и функциями на уровне всей программы, а не частично, как это можно сделать в ООП или процедурных языках.

Что такое функциональное программирование
Что такое функциональное программирование

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

Есть ряд определений, которые делают функциональное программирование отдельной парадигмой и стилем создания алгоритмов:

  1. Функция или идентифицированный блок кода. Это основа функционального программирования. Функция является блоком кода, состоящего из одной или нескольких инструкций. В функциональных языках, каждая инструкция становится функцией. Весь алгоритм состоит из их описания и вызовов. Все программы состоят из отдельных мелких блоков. Крупные блоки состоят из множества мелких. А за выполнение основного алгоритма также отвечает главная функция.
  2. Чистые функции. Блоки кода, которые управляют только своим контекстом и не влияют на глобальный контекст (состояние) всей программы.
  3. Лямбда функции. Это анонимные функции или лямбда выражения, прописываются как отдельные инструкции, объявляющиеся в том месте контекста, где они будут применяться. Не идентифицируются именами и вызываются автоматически другими функциями. Лямбда выражения – сокращенная запись функций.
  4. Функции высшего порядка. Функции, управляющие другими функциями. Их аргументы это — анонимные и чистые функции. Они же могут возвращаться в качестве результата после выполнения главной функции.
  5. Композитные функции. Один блок влияет за результат, возвращаемый другим.
  6. Константы. В функциональных языках программирования нет переменных, но есть неизменяемые константы.
  7. Замыкания. Определение тела блока в контексте другого. Локальная функция имеет доступ к данным родительской, а также переопределяется при каждом ее вызове.
  8. Рекурсия. В функциональных языках рекурсия выступает в качестве замены циклам. Это вызов функцией самой себя в своем же контексте. Повтор таких вызовов осуществляется до тех пор, пока управляющее условие не достигнет true.
Что такое функциональное программирование
Отличие простых функций от лямбда выражений

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

Существует функциональный стиль программирования и парадигма, основанная на строгих правилах. Функциональные языки поддерживают парадигму ФП со всеми её правилами написания программ. В императивных языках (процедурных или ООП) можно программировать в мультипарадигменном стиле. То есть использовать в описании программ различные стили. На таких языках доступно программирование и в функциональном стиле. То есть, появляется возможность применять все те методы описания программ, что и в функциональных языках.

Современным и эффективным функциональным языком программирования является Haskell. Это чистый функциональный язык, использующий все преимущества ФП. Такие языки, как JavaScript, Python, C++ и другие, позволяют программисту писать программы в функциональном стиле, ввиду наличия для этого всех необходимых языковых конструкций. Преимущества таких языков в том, что у программиста есть выбор между способами организации кода. Когда функциональный язык предоставляет всего один ограниченный способ.

Что такое функциональное программирование
Язык программирования Haskell

Что касается организации кода в ФП. Если брать императивные или функциональные языки, то программа разделяется на отдельные блоки, то есть — функции. В ООП такими блоками становятся классы. Объекты классов взаимодействуют друг с другом, когда в ФП взаимодействуют функции. В императивных языках функции могут влиять на глобальные переменные и другие структуры данных. В функциональном языке, блоки никак не касаются глобального контекста — они и создают этот глобальный контекст.

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

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

Есть ряд языков, которые поддерживают исключительно функциональную парадигму:

  1. Haskell. Вобрал в себя все, что заложено в ФП. Его предназначение -выполнение сложных вычислений. Не используется для разработки графических интерфейсов. Многие разработчики применяют его в связке с императивными языками.
  2. APL — функциональный язык, предок таких вычислительных технологий, как MathLab.
  3. F# — функциональный язык, применяемый совместно с платформой .NET от Microsoft.
  4. Miranda — предок языка Haskell.

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

Хотя функциональная парадигма и определяет ряд строгих принципов организации кода, программисту, пишущему программы на императивных языках, нет нужды соблюдать эти строгие правила. Вполне ФП можно реализовать на языках JavaScript, PHP, C, C# и других. Особенно JavaScript обладает самыми широкими возможностями для ФП, так как функциям в нем создатели придали очевидную гибкость. Здесь есть замыкания, анонимные функции, генераторы и другие гибкие приемы, которые отсутствуют в остальных императивных языках.

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

Что такое функциональное программирование
Принципы функционального программирования

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

Переменные и функции

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

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

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

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

Пример

const a = 5;
const b = 5;
function digitSum(x, y){
x++;
y++;
console.log(x+y);
}
digitSum(10, 12); // 24 — сложение локальных переменных
digitSum(a, b); // 12 — значения из констант в качестве аргументов
console.log(a); // 5 — константа осталась не измененной

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

Состояние программы — это значение всех глобальных переменных в ходе выполнения программы. Неизменным состоянием является тогда, когда значения глобальных переменных не изменяются в ходе выполнения программы.

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

Рекурсия

В функциональных языках отсутствуют циклы — создатели не включили циклы в языки такого характера по причине их ненужности. Поэтому в них используют рекурсию. Рекурсия — функция, которая при выполнении вызывает саму себя до срабатывания условия. Также в ФП отсутствуют условные конструкции if else. Циклы и условные конструкции также могут влиять на контекст блока, выдавая разные результаты при изменении состояния глобального контекста, влияющего на состояние блока.

Что такое функциональное программирование
Рекурсия

Пример рекурсии

function toSquare(digit, n) {
if (n == 1) {
return digit;
} else {
return digit * toSquare(digit, n — 1);
}
}
console.log( square(3, 2) ); // 9

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

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

Пример

// глобальная область видимости
function a(){}
function b(){} // блоки кода первого класса глобального контекста
var f = function(x,y){return x*y} // function expression
f(5,5); // 25

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

Это функции, которые в качестве параметров принимают другие функции, определенные ранее или анонимные.

Пример

function d(f){
f();
}
d(function(){return 5}); // 5 — в параметре используется анонимная локальная функция

Пример с замыканием

function locked(x,y){ // функция высшего порядка
function inner(){ // определяемый блок в теле функции при каждом ее вызове
return x+y; // возвращаемый результат, при этом, внутренний блок имеет доступ к аргументам родительского
}
inner(); // вызов внутреннего блока при каждом вызове родительского
}
// Замыкание инкапсулирует внутренние данные
function lock(){
var x = 10;
return function(){
x++;
return x;
}
}
var f = lock(); // function expression сохраняем тело внутренней функции в переменную за счет присвоения вызова f. Внутренняя функция имеет доступ к локальной переменной x внешней функции

f(); // 11
f(); // 12
f(); // 13
f(); // 14

Функции высшего класса способны запускать цепочку из комплекса функций. Чаще всего основной алгоритм программы в ФП вмещается всего в одну функцию высшего порядка. Примером такой является метод Main в Java или C#. Однако в ФП программист вручную создает функции высшего порядка, а функциональных ЯП, компилятор сам определяет порядок выполнения внутренних блоков.

Композиция функций

Это порядок выполнения функций, в котором результат от одной функции передается другой. В итоге, программист запускает целую цепочку блоков, которая приводит к одному результату. Цепочку можно организовать записью последовательности их выполнения в теле функции высшего порядка. Или задать функциональные аргументы, где программист будет сам определять, в какой последовательности будут вызываться блоки. В императивных языках еще есть способ задания цепочки посредством оператора «.» и выглядит это следующим образом: Main().func1().func2().func3()

Что такое функциональное программирование
Взаимодействие функций

В функциональном языке такая операция организуется так:

function Main(f1, f2, f3){
const c;
const z;
const r;
f1(f2(f3())); // цепочка вызовов для получения одного результата
}
function f1(f2, f3){
const a = 5;
c = a+2; // 7
f2(f3);
}

function f2(f3){
z = a+3 // 8
f3();
}
function f3(){
r = c+z // 15
return r;
}
Main(f1, f2, f3); // 15

Преимущества функционального программирования

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

  1. Простота в отладке. Неизменяемость данных и использование чистых функций упрощают обнаружение ошибок и их исправление. Когда в императивных языках состояние программы может не только усложнить поиск и исправление ошибки, но также влиять и на результат выполнения программы или самого простого блока.
  2. Ленивые вычисления. Функциональные языки пользуются технологий ленивых вычислений. Компилятор сам решает, какие блоки выполнять, а какие отложить на потом, какие выполнять в многозадачном режиме. Это оптимизирует выполнение программы, влияя на увеличение скорости.
  3. Деление программы на модули. Отдельные блоки кода делят программу на составляющие, называемые модулями. Это облегчает ее отладку и читаемость кода. При этом, отдельные блоки выполняют отдельные закрытые от внешнего состояния операции. Модули можно сравнить с классами в объектно-ориентированом программировании, которые также разделяют программу на составляющие, которые ассоциируют с объектами физического мира.
  4. Облегченная читаемость кода. Изоляция блоков от глобального состояния программы улучшает читаемость кода в целом. Иногда даже не нужно изучать тело блока, чтобы понять, для чего он предназначен.
  5. Многопоточность. Состояние программы не меняется, если за одну единицу времени выполняется несколько чистых функций. Это позволяет увеличить скорость выполнения программы. При этом, в функциональных языках есть все необходимые инструменты для реализации многопоточности. Функциональная программа сразу становится готова к параллельному выполнению. Не нужно заботиться о времени выполнения того или иного блока. Можно добавлять любое количество потоков в программу и не заботиться о изменении её состояния, присущего ООП и процедурным языкам.

Одним из важных преимуществ ФП является Unit тестирование. Единственное, что может повлиять на возвращаемое значение блока — передаваемый в него аргумент. Это позволяет Unit-тестировщикам проводить тестирование каждой функции, используя только нужные аргументы. Им не нужно строить алгоритмы из блоков или менять глобальное состояние всей программы. Необходимо только проводить тесты с аргументами, которые соответствуют конкретным случаям.

Если все функции в программе проходят Unit-тесты, то программист может не беспокоиться о качестве ПО, чем в случае императивных языков программирования. В Java или PHP проверки возвращаемого значения не достаточно — блок может поменять внешнее состояние, которое тоже подлежит проверке. В ФП такой проблемы нет. Как только вы воспроизведёте ошибку, найти её источник — тривиальная задача. Это даже приятно. Как только вы остановите выполнение программы, перед вами будет весь стек вызовов. Вы можете просмотреть аргументы вызова каждой функции, прямо как в императивном языке. С тем отличием, что в императивной программе этого не достаточно, ведь функции зависят от значений полей, глобальных переменных и состояний других классов.

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

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

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

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

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

Примером может выступать приложение WhatsApp. У этого приложения более 900 миллионов пользователей. Однако сервис поддерживается всего 50 инженерами, использующих функциональный язык Erlang, обрабатывая огромное количество запросов и потоков за единицу времени. Это говорит о том, что ФП применяется в тех системах, которые обслуживают миллионное количество пользователей. Также ФП используют в экономике и науке, когда создают высоконагруженные системы исчислений BigData и DataScience, основанные на искусственном интеллекте.

Что такое функциональное программирование
Приложение WhatsApp написано на функциональном языке

В настоящее время это не единственные области применения ФП и ФЯ. Производство, разработка ПО и многое другое, не ограничивается одним лишь императивным программированием. Специалисты с большим энтузиазмом лучше знают где какую парадигму лучше применить для выгоды бизнеса. Программисты, владеющие ФП, более востребованы в различных областях IT индустрии.

Айтистанция
Добавить комментарий