Функциональное программирование в TypeScript: основы и примеры

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Функциональное программирование в TypeScript: от теории к практике

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

1. Чистые функции и иммутабельность

Чистая функция — это функция, которая:

  • Всегда возвращает один и тот же результат для одних и тех же аргументов (детерминированность).
  • Не имеет побочных эффектов (не изменяет внешние переменные, не выполняет I/O операции).
// Чистая функция
const add = (a: number, b: number): number => a + b;

// Нечистая функция (побочный эффект — изменение внешнего состояния)
let counter = 0;
const increment = (): number => ++counter;

Иммутабельность означает, что данные не изменяются, а создаются новые копии. В TypeScript можно использовать оператор spread и методы без мутаций:

interface User {
  name: string;
  age: number;
}

// Мутация (плохо)
const updateAgeMutable = (user: User, newAge: number): void => {
  user.age = newAge;
};

// Иммутабельный подход (хорошо)
const updateAgeImmutable = (user: User, newAge: number): User => ({
  ...user,
  age: newAge,
});

Иммутабельность упрощает отладку и тестирование, так как данные всегда предсказуемы.

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

Функции высшего порядка — это функции, которые принимают другие функции как аргументы или возвращают их. Пример — метод Array.map:

const numbers: number[] = [1, 2, 3, 4];
const doubled: number[] = numbers.map((x) => x * 2);
console.log(doubled); // [2, 4, 6, 8]

Замыкание — это функция, которая "запоминает" переменные из внешней области видимости даже после того, как внешняя функция завершила выполнение:

const createMultiplier = (factor: number): ((x: number) => number) => {
  return (x: number) => x * factor;
};

const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

Замыкания широко используются для создания функций с частичным применением аргументов (каррирование).

3. Композиция функций и каррирование

Композиция функций — это объединение нескольких функций в одну, где выход одной функции является входом для следующей. В TypeScript можно написать утилиту для композиции:

const compose = <T, R>(...fns: Array<(arg: T) => T>) => (arg: T): T => 
  fns.reduceRight((acc, fn) => fn(acc), arg);

// Пример: преобразование строки
const toUpperCase = (s: string): string => s.toUpperCase();
const exclaim = (s: string): string => `${s}!`;
const shout = compose(toUpperCase, exclaim);

console.log(shout("hello")); // "HELLO!"

Каррирование — это преобразование функции с несколькими аргументами в последовательность функций с одним аргументом:

// Обычная функция
const add = (a: number, b: number): number => a + b;

// Каррированная версия
const curriedAdd = (a: number) => (b: number): number => a + b;

const add5 = curriedAdd(5);
console.log(add5(3)); // 8

Каррирование упрощает создание специализированных функций и повышает переиспользуемость кода.

4. Монады и работа с Option/Maybe

Монада — это паттерн функционального программирования, который инкапсулирует значение и предоставляет интерфейс для его преобразования. Одна из простейших монад — Maybe (или Option), которая безопасно обрабатывает null и undefined:

// Простая реализация Maybe
type Maybe<T> = { kind: "some"; value: T } | { kind: "none" };

const Some = <T>(value: T): Maybe<T> => ({ kind: "some", value });
const None = <T>(): Maybe<T> => ({ kind: "none" });

const map = <T, R>(maybe: Maybe<T>, fn: (val: T) => R): Maybe<R> => {
  if (maybe.kind === "some") {
    return Some(fn(maybe.value));
  }
  return None();
};

// Пример использования
const safeDivide = (a: number, b: number): Maybe<number> => {
  if (b === 0) return None();
  return Some(a / b);
};

const result = safeDivide(10, 2);
const doubledResult = map(result, (x) => x * 2);
console.log(doubledResult); // { kind: "some", value: 10 }

TypeScript позволяет строго типизировать такие структуры, что делает код безопаснее. Для продакшена можно использовать библиотеки вроде fp-ts или monet.

Заключение

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

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

Начните внедрять эти принципы постепенно: замените мутации на иммутабельные обновления, используйте map/filter/reduce вместо циклов. Со временем вы заметите, что код становится более декларативным и легким для поддержки. TypeScript с его строгой типизацией — идеальный язык для освоения функционального программирования в веб-разработке.

Блоги

Рекомендаций по книгам