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

Ответ

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

Базовый пример:

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

// Каррированная версия
const curriedAdd = a => b => c => a + b + c;

console.log(add(1, 2, 3));        // 6
console.log(curriedAdd(1)(2)(3)); // 6

Практическое применение в Node.js:

// Каррирование для создания middleware с конфигурацией
const createLogger = (serviceName) => (logLevel) => (message) => {
  console.log(`[${serviceName}] [${logLevel.toUpperCase()}] ${message}`);
};

const authServiceLogger = createLogger('AuthService');
const authErrorLogger = authServiceLogger('error');
const authInfoLogger = authServiceLogger('info');

// Использование специализированных логгеров
authErrorLogger('Invalid token provided'); // [AuthService] [ERROR] Invalid token provided
authInfoLogger('User login successful');   // [AuthService] [INFO] User login successful

// Каррирование для работы с базами данных
const createQuery = (dbClient) => (tableName) => (conditions) => {
  return dbClient.query(`SELECT * FROM ${tableName} WHERE ?`, [conditions]);
};

const queryUsers = createQuery(dbClient)('users');
const activeUsersQuery = queryUsers({ status: 'active' });
// activeUsersQuery - готовая к выполнению функция с предустановленными параметрами

Утилита для каррирования:

// Универсальная функция каррирования
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      };
    }
  };
}

// Использование
const multiply = (a, b, c) => a * b * c;
const curriedMultiply = curry(multiply);

const double = curriedMultiply(2);
const doubleAndTriple = double(3);

console.log(doubleAndTriple(4)); // 24 (2 * 3 * 4)

Преимущества каррирования в Node.js:

  1. Переиспользование кода: Создание специализированных функций из общих
  2. Композиция: Упрощает создание цепочек вызовов и композицию функций
  3. Отложенное выполнение: Можно подготовить функцию с частью аргументов и выполнить позже
  4. Чистые функции: Каррированные функции легче тестировать и отлаживать

Ответ 18+ 🔞

Ну ты представляешь, эта ваша функциональщина с её каррированием — выглядит как какая-то магия для непосвящённых, но на деле-то всё просто, как три копейки. Сейчас разжуём.

Вот смотри, обычная функция — это как бутерброд: взял сразу все ингредиенты, собрал и съел. А каррированная — это как если бы тебе сначала дали хлеб, потом, через пять минут, масло, а потом ещё через десять — колбасу. По одному кусочку за раз. Выглядит, конечно, как издевательство, но иногда так удобнее.

Базовый пример, чтобы въехать:

// Нормальная, человеческая функция
const add = (a, b, c) => a + b + c;

// А это её каррированный уродец
const curriedAdd = a => b => c => a + b + c;

console.log(add(1, 2, 3));        // 6 — быстро и понятно
console.log(curriedAdd(1)(2)(3)); // 6 — тот же результат, но вызовов как у многоэтажки

Суть в чём? Вместо того чтобы ждать все три аргумента сразу, функция curriedAdd принимает первый, возвращает новую функцию, которая ждёт второй, и так далее. Поначалу кажется, что это пиздопроебина полная, зачем так усложнять? А потом находишь кейсы, где это реально спасает жопу.

Вот смотри, как это в реальной жизни применить, например, в логгере:

// Каррирование для создания middleware с конфигурацией
const createLogger = (serviceName) => (logLevel) => (message) => {
  console.log(`[${serviceName}] [${logLevel.toUpperCase()}] ${message}`);
};

// Сперва создаём логгер для сервиса авторизации
const authServiceLogger = createLogger('AuthService');
// Потом из него делаем специализированный логгер для ошибок
const authErrorLogger = authServiceLogger('error');
// И для информационных сообщений
const authInfoLogger = authServiceLogger('info');

// Используем готовые, заточенные под конкретную задачу функции
authErrorLogger('Invalid token provided'); // [AuthService] [ERROR] Invalid token provided
authInfoLogger('User login successful');   // [AuthService] [INFO] User login successful

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

Или вот работа с базой данных — классика жанра:

const createQuery = (dbClient) => (tableName) => (conditions) => {
  return dbClient.query(`SELECT * FROM ${tableName} WHERE ?`, [conditions]);
};

// Цепочка: сначала клиент БД, потом таблица, потом условия
const queryUsers = createQuery(dbClient)('users');
const activeUsersQuery = queryUsers({ status: 'active' });
// activeUsersQuery — это уже готовая, заряженная функция, которую можно выполнить когда угодно

А если лень каждый раз писать эти стрелочки-закорючки, есть универсальная утилитка:

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      };
    }
  };
}

// Использовать — проще пареной репы
const multiply = (a, b, c) => a * b * c;
const curriedMultiply = curry(multiply);

const double = curriedMultiply(2);          // Частично применили первый аргумент
const doubleAndTriple = double(3);          // Применили второй

console.log(doubleAndTriple(4)); // 24 (2 * 3 * 4) — и вот финальный расчёт

Так зачем весь этот геморрой, спросишь? А вот зачем, чувак:

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

В общем, инструмент хоть и специфичный, но в арсенале иметь стоит. Особенно когда проект растёт и нужно поддерживать в нём хоть какой-то порядок, а не бардак ебаный.