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

👨‍💻 Frontend Developer 🟠 Может встретиться 🎚️ Сложный
#Теория

Краткий ответ

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

  1. Чистые функции (Pure Functions): Функция является чистой, если ее возвращаемое значение зависит только от ее аргументов, и у нее нет наблюдаемых побочных эффектов (например, изменения глобальных переменных или вывода в консоль).
  2. Неизменяемость (Immutability): Данные после их создания не могут быть изменены. Вместо изменения существующих структур данных создаются новые.
  3. Функции как граждане первого класса (First-Class Citizens): Функции можно присваивать переменным, передавать в качестве аргументов другим функциям и возвращать из них. Функции, которые принимают или возвращают другие функции, называются функциями высшего порядка (Higher-Order Functions).
  4. Композиция функций (Function Composition): Процесс объединения нескольких простых функций для создания более сложной. Результат одной функции передается в качестве аргумента следующей.

Развернутый ответ с примерами

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

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

Пример нечистой функции:

let discount = 0.1; // Внешнее состояние
 
function calculateDiscountedPrice(price) {
  // Зависит от внешнего состояния `discount`
  return price - price * discount;
}

Пример чистой функции:

function calculateDiscountedPrice(price, discount) {
  // Результат зависит только от аргументов
  return price - price * discount;
}
 
console.log(calculateDiscountedPrice(100, 0.1)); // 90
console.log(calculateDiscountedPrice(100, 0.1)); // 90 (всегда тот же результат)

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

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

Пример с изменением (мутацией):

const user = { name: 'Алиса', age: 25 };
 
function celebrateBirthday(person) {
  // Прямое изменение объекта (мутация)
  person.age++;
  return person;
}
 
celebrateBirthday(user);
console.log(user); // { name: 'Алиса', age: 26 } (исходный объект изменен)

Пример с неизменяемостью:

const user = { name: 'Алиса', age: 25 };
 
function celebrateBirthday(person) {
  // Создаем новый объект вместо изменения старого
  return {
    ...person,
    age: person.age + 1
  };
}
 
const updatedUser = celebrateBirthday(user);
console.log(user);        // { name: 'Алиса', age: 25 } (исходный объект нетронут)
console.log(updatedUser); // { name: 'Алиса', age: 26 } (возвращен новый объект)

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

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

Пример:

Методы массивов, такие как map, filter и reduce, являются классическими примерами функций высшего порядка.

const numbers = [1, 2, 3, 4, 5];
 
// `filter` принимает функцию-предикат в качестве аргумента
const evenNumbers = numbers.filter(function(n) {
  return n % 2 === 0;
});
 
console.log(evenNumbers); // [2, 4]
 
// Функция, которая возвращает другую функцию
function createMultiplier(factor) {
  return function(number) {
    return number * factor;
  };
}
 
const double = createMultiplier(2);
const triple = createMultiplier(3);
 
console.log(double(5)); // 10
console.log(triple(5)); // 15

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

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

Пример:

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

const toUpperCase = (str) => str.toUpperCase();
const addExclamation = (str) => `${str}!`;
 
// Вместо вложенных вызовов: addExclamation(toUpperCase('hello'))
// можно использовать композицию.
 
// Простая функция композиции
const compose = (f, g) => (x) => f(g(x));
 
const shout = compose(addExclamation, toUpperCase);
 
console.log(shout('hello world')); // 'HELLO WORLD!'

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

Применение этих принципов делает код более декларативным (мы описываем, что нужно сделать, а не как), надежным и легко масштабируемым.