Урок 08

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

Каррирование

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

// Обычная
const add = (a, b) => a + b;
add(1, 2); // 3

// Каррированная
const addCurried = (a) => (b) => a + b;
addCurried(1)(2); // 3

Зачем

Создание специализированных функций:

const multiply = (a) => (b) => a * b;

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

double(5); // 10
triple(5); // 15

Каррирование вручную

function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        }
        return (...next) => curried.apply(this, [...args, ...next]);
    };
}

const add3 = (a, b, c) => a + b + c;
const curriedAdd = curry(add3);

curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6
curriedAdd(1)(2, 3); // 6
curriedAdd(1, 2, 3); // 6

fn.length — число параметров до первого со значением по умолчанию.

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

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

function partial(fn, ...fixedArgs) {
    return (...restArgs) => fn(...fixedArgs, ...restArgs);
}

const greet = (greeting, name) => `${greeting}, ${name}`;
const sayHello = partial(greet, 'Привет');

sayHello('Алиса'); // 'Привет, Алиса'
sayHello('Боб');   // 'Привет, Боб'

Bind как частичное

bind может фиксировать аргументы:

const greet = (greeting, name) => `${greeting}, ${name}`;
const sayHi = greet.bind(null, 'Привет');

sayHi('Алиса'); // 'Привет, Алиса'

Каррирование vs частичное

  • Каррирование — превращает f(a, b) в f(a)(b)
  • Частичное применение — фиксирует часть аргументов

Частичное — более общее. Каррирование — частный случай.

Практика: логгер

const log = (level) => (message) => {
    console.log(`[${level}] ${message}`);
};

const info = log('INFO');
const error = log('ERROR');

info('Запуск');  // [INFO] Запуск
error('Сбой');   // [ERROR] Сбой

Практика: фильтры

const filterBy = (key) => (value) => (items) =>
    items.filter((item) => item[key] === value);

const byRole = filterBy('role');
const admins = byRole('admin');

admins(users);

Практика: API-клиент

const apiRequest = (baseUrl) => (endpoint) => (options = {}) =>
    fetch(`${baseUrl}${endpoint}`, options).then((r) => r.json());

const api = apiRequest('https://api.example.com');
const getUsers = api('/users');
const getPosts = api('/posts');

getUsers();
getPosts();

Практика: валидация

const minLength = (min) => (value) =>
    value.length >= min ? null : `Минимум ${min} символов`;

const min8 = minLength(8);
const min3 = minLength(3);

min8('abc');     // 'Минимум 8 символов'
min8('abcdefgh'); // null

Композиция

const compose = (...fns) => (x) =>
    fns.reduceRight((acc, fn) => fn(acc), x);

const addOne = (x) => x + 1;
const double = (x) => x * 2;

const addThenDouble = compose(double, addOne);
addThenDouble(3); // 8

Когда использовать

  • Много повторяющихся конфигураций
  • Создание специализированных функций из общих
  • Функциональный стиль
  • Пайплайны обработки

Не переусложняй: часто обычная функция с параметрами понятнее.

Итоги

  • Каррирование — цепочка функций с одним аргументом
  • Частичное — фиксация части аргументов
  • bind — простой способ частичного
  • Полезно для логгеров, фильтров, API
  • Не везде уместно