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

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

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

Начать курс

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

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

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

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

Чистые функции (Pure Functions)

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

  • Всегда возвращает один и тот же результат для одних и тех же аргументов (детерминированность).
  • Не имеет побочных эффектов (не изменяет внешние переменные, не выводит данные в консоль, не отправляет запросы).

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

function add(a: number, b: number): number {
  return a + b;
}

// Всегда предсказуемо
console.log(add(2, 3)); // 5
console.log(add(2, 3)); // 5

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

let counter = 0;
function increment(): number {
  return ++counter; // Побочный эффект: изменение внешней переменной
}

Неизменяемость (Immutability)

Данные не должны изменяться после создания. Вместо изменения существующего объекта мы создаем его новую копию с необходимыми изменениями.

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

const user: User = { name: "Alice", age: 30 };

// Плохо (мутация)
user.age = 31;

// Хорошо (иммутабельность)
const updatedUser: User = { ...user, age: 31 };

console.log(user); // { name: "Alice", age: 30 } - оригинал не изменился
console.log(updatedUser); // { name: "Alice", age: 31 }

Композиция и каррирование в TypeScript

Композиция функций — это процесс объединения нескольких простых функций для создания более сложной. Каррирование (currying) — это трансформация функции с несколькими аргументами в последовательность функций, каждая из которых принимает один аргумент.

Композиция (Function Composition)

Типизированная реализация композиции в TypeScript:

function compose<A, B, C>(f: (x: B) => C, g: (x: A) => B): (x: A) => C {
  return (x: A) => f(g(x));
}

const toUpperCase = (str: string): string => str.toUpperCase();
const exclaim = (str: string): string => `${str}!`;

const shout = compose(exclaim, toUpperCase);
console.log(shout("hello world")); // "HELLO WORLD!"

Каррирование (Currying)

Пример каррированной функции:

function multiply(a: number): (b: number) => number {
  return (b: number) => a * b;
}

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

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

Каррирование делает функции более гибкими и удобными для композиции.

Работа с монадами: Option и Result

Монады — это мощный паттерн для работы с побочными эффектами и обработкой ошибок без нарушения чистоты функций. В TypeScript часто используются монады Option (или Maybe) и Result (или Either).

Монада Option (Maybe)

Позволяет безопасно работать со значениями, которые могут быть null или undefined.

type Option<T> = T | null;

function safeDivide(a: number, b: number): Option<number> {
  return b === 0 ? null : a / b;
}

function getValue(value: Option<number>): string {
  if (value === null) {
    return "Ошибка: деление на ноль";
  }
  return `Результат: ${value}`;
}

console.log(getValue(safeDivide(10, 2))); // "Результат: 5"
console.log(getValue(safeDivide(10, 0))); // "Ошибка: деление на ноль"

Монада Result (Either)

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

type Result<T, E> = { success: true; value: T } | { success: false; error: E };

function parseJSON(jsonString: string): Result<any, string> {
  try {
    const parsed = JSON.parse(jsonString);
    return { success: true, value: parsed };
  } catch (e) {
    return { success: false, error: "Некорректный JSON" };
  }
}

const result = parseJSON('{"name": "Bob"}');
if (result.success) {
  console.log(result.value); // { name: "Bob" }
} else {
  console.error(result.error);
}

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

Рассмотрим несколько практических примеров, демонстрирующих преимущества ФП.

Обработка массивов

Вместо циклов for и мутаций используем методы map, filter и reduce:

const numbers: number[] = [1, 2, 3, 4, 5, 6];

// Трансформация
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10, 12]

// Фильтрация
const evens = numbers.filter(n => n % 2 === 0); // [2, 4, 6]

// Агрегация
const sum = numbers.reduce((acc, n) => acc + n, 0); // 21

// Комбинация
const result2 = numbers
  .filter(n => n % 2 === 0)
  .map(n => n * 10)
  .reduce((acc, n) => acc + n, 0); // 120

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

Функции, которые принимают другие функции в качестве аргументов или возвращают их:

function withLogging<T>(fn: (...args: any[]) => T): (...args: any[]) =>                

Блоги

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