Функциональное программирование в JavaScript: продвинутые техники

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

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

Начать курс

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

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

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

1. Каррирование и частичное применение

Что такое каррирование?

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

// Обычная функция
const add = (a, b, c) => a + b + c;
console.log(add(1, 2, 3)); // 6

// Каррированная версия
const curriedAdd = a => b => c => a + b + c;
console.log(curriedAdd(1)(2)(3)); // 6

// Универсальная функция каррирования
const curry = (fn) => {
  const arity = fn.length;
  const resolver = (...args) => {
    if (args.length < arity) {
      return (...nextArgs) => resolver(...args, ...nextArgs);
    }
    return fn(...args);
  };
  return resolver;
};

const multiply = (a, b, c) => a * b * c;
const curriedMultiply = curry(multiply);
console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2, 3)(4)); // 24

Частичное применение

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

const partial = (fn, ...presetArgs) => (...laterArgs) => fn(...presetArgs, ...laterArgs);

const greet = (greeting, name) => `${greeting}, ${name}!`;
const sayHello = partial(greet, 'Hello');
const sayHi = partial(greet, 'Hi');

console.log(sayHello('Alice')); // Hello, Alice!
console.log(sayHi('Bob'));     // Hi, Bob!

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

Продвинутая композиция

Композиция — это процесс комбинирования нескольких функций для создания новой функции. В продвинутом ФП мы используем композицию справа налево (compose) и слева направо (pipe).

// Простая композиция (справа налево)
const compose = (...fns) => (value) => fns.reduceRight((acc, fn) => fn(acc), value);

// Композиция слева направо (pipe)
const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);

// Пример: обработка строки
const trim = (str) => str.trim();
const toLowerCase = (str) => str.toLowerCase();
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
const removeSpaces = (str) => str.replace(/\s+/g, '-');

const slugify = compose(capitalize, toLowerCase, trim, removeSpaces);
// Эквивалент: removeSpaces(trim(toLowerCase(capitalize("  Hello World  "))))

console.log(slugify('  Hello World  ')); // Hello-world

Трансдьюсеры (Transducers)

Трансдьюсеры — это композиция функций-трансформаций, которые работают с любыми итерируемыми структурами данных. Они позволяют объединить несколько операций (например, map и filter) в один проход.

const transducer = (mapper, predicate) => (reducer) => (acc, value) => {
  const mapped = mapper(value);
  return predicate(mapped) ? reducer(acc, mapped) : acc;
};

const double = (x) => x * 2;
const isEven = (x) => x % 2 === 0;

const processNumbers = transducer(double, isEven);
const sumReducer = (acc, val) => acc + val;

const numbers = [1, 2, 3, 4, 5];
const result = numbers.reduce(processNumbers(sumReducer), 0);
console.log(result); // 12 (удвоенные четные: 4 + 8)

3. Монады и функторы

Функторы

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

// Простой функтор
const Functor = (value) => ({
  map: (fn) => Functor(fn(value)),
  valueOf: () => value,
});

const result = Functor(5)
  .map((x) => x * 2)
  .map((x) => x + 3);

console.log(result.valueOf()); // 13

Монада Maybe (Option)

Монада Maybe позволяет безопасно обрабатывать значения, которые могут быть null или undefined. Она состоит из двух подтипов: Just (значение есть) и Nothing (значения нет).

class Maybe {
  static just(value) {
    return new Just(value);
  }
  static nothing() {
    return new Nothing();
  }
  static fromNullable(value) {
    return value !== null && value !== undefined
      ? Maybe.just(value)
      : Maybe.nothing();
  }
}

class Just extends Maybe {
  constructor(value) {
    super();
    this._value = value;
  }
  map(fn) {
    return Maybe.fromNullable(fn(this._value));
  }
  getOrElse(defaultValue) {
    return this._value;
  }
}

class Nothing extends Maybe {
  map(fn) {
    return this;
  }
  getOrElse(defaultValue) {
    return defaultValue;
  }
}

// Пример использования
const safeGet = (obj, path) => {
  const keys = path.split('.');
  let current = obj;
  for (const key of keys) {
    if (current == null) return Maybe.nothing();
    current = current[key];
  }
  return Maybe.fromNullable(current);
};

const user = { name: 'Alice', address: { city: 'Moscow' } };
const city = safeGet(user, 'address.city')
  .map((c) => c.toUpperCase())
  .getOrElse                

Блоги

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