В Stripe SDK форматирование суммы зависит от валюты, а валюта известна при инициализации — до того как суммы станут известны. Каррирование позволяет «заморозить» первый аргумент и получить специализированную функцию: formatRUB = formatCurrency('RUB'), затем formatRUB(1500). Это мощный паттерн для создания переиспользуемых специализированных функций.
Когда часть аргументов функции известна заранее, а часть — нет, каррирование позволяет зафиксировать известные и создать новую функцию только для оставшихся. Меньше повторений, более читаемый код.
// Обычная функция
function add(a, b) { return a + b }
add(3, 4) // 7
// Каррированная версия
const addC = a => b => a + b
addC(3)(4) // 7
// Создаём специализированные функции
const add5 = addC(5) // фиксируем первый аргумент
add5(10) // 15
add5(20) // 25const multiply = a => b => a * b
const double = multiply(2)
const triple = multiply(3)
double(7) // 14
triple(7) // 21
[1, 2, 3, 4, 5].map(double) // [2, 4, 6, 8, 10]
[1, 2, 3, 4, 5].map(triple) // [3, 6, 9, 12, 15]// Форматирование валюты — currency известна при инициализации
const formatCurrency = currency => amount =>
new Intl.NumberFormat('ru-RU', { style: 'currency', currency }).format(amount)
const formatRUB = formatCurrency('RUB')
const formatUSD = formatCurrency('USD')
formatRUB(1500) // '1 500,00 ₽'
formatUSD(29.99) // '29,99 $'
const prices = [1000, 2500, 750]
prices.map(formatRUB) // ['1 000,00 ₽', '2 500,00 ₽', '750,00 ₽']
// Логгер с preset-уровнем — уровень известен при настройке
const log = level => message => console.log(`[${level}] ${message}`)
const info = log('INFO')
const error = log('ERROR')
const warn = log('WARN')
info('Сервер запущен') // [INFO] Сервер запущен
error('Нет соединения') // [ERROR] Нет соединения
// Валидатор с конфигурацией
const minLength = min => str => str.length >= min
const maxLength = max => str => str.length <= max
const hasDigit = () => str => /\d/.test(str)
const isValidPassword = str =>
minLength(8)(str) && maxLength(32)(str) && hasDigit()(str)
isValidPassword('secret123') // true
isValidPassword('hi') // false — слишком короткий
isValidPassword('noDIGITS') // false — нет цифрыАвтоматически каррирует любую функцию с любым числом аргументов:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args) // аргументов достаточно — вызываем
}
return function(...moreArgs) {
return curried.apply(this, args.concat(moreArgs)) // накапливаем
}
}
}
const sum = curry((a, b, c) => a + b + c)
sum(1)(2)(3) // 6
sum(1, 2)(3) // 6 — можно группировать
sum(1)(2, 3) // 6
sum(1, 2, 3) // 6 — как обычная функцияf(a)(b)(c)bind или замыкание// Частичное применение через bind
const clamp = (min, max, value) => Math.min(Math.max(value, min), max)
const clamp0to100 = clamp.bind(null, 0, 100) // фиксируем min и max
clamp0to100(50) // 50
clamp0to100(-10) // 0
clamp0to100(200) // 100Ошибка 1: путаница с порядком аргументов
// Неудачный порядок — конфигурация в конце, данные первыми
const replace = (str, pattern, replacement) =>
str.replace(pattern, replacement)
// Не каррируется удобно — str меняется, а pattern обычно фиксирован
// Удачный порядок — конфигурация первая, данные последними
const replace2 = pattern => replacement => str =>
str.replace(pattern, replacement)
const removeTags = replace2(/<[^>]+>/g)('') // фиксируем конфигурацию
removeTags('<b>Hello</b>') // 'Hello'
removeTags('<p>World</p>') // 'World'Ошибка 2: каррирование функций с rest-аргументами
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) return fn.apply(this, args)
return (...more) => curried(...args, ...more)
}
}
// fn.length не работает для rest-параметров
const variadic = (...args) => args.reduce((a, b) => a + b, 0)
console.log(variadic.length) // 0 — fn.length игнорирует rest!
// Для вариадических функций используй ручное каррированиеcreateCharge(currency)(amount)(description)connect(mapState)(mapDispatch)(Component) (Redux)log(level)(context)(message)Каррированные функции для интернет-магазина: форматирование цен, фильтрация, логирование
// 1. Форматирование цен с конфигурацией
const formatCurrency = currency => amount =>
new Intl.NumberFormat('ru-RU', { style: 'currency', currency }).format(amount)
const formatRUB = formatCurrency('RUB')
const formatUSD = formatCurrency('USD')
const prices = [1000, 2500, 750]
console.log(prices.map(formatRUB))
// ['1 000,00 ₽', '2 500,00 ₽', '750,00 ₽']
console.log(formatUSD(29.99)) // '29,99 $'
// 2. Каррированный фильтр по полю объекта
const byField = field => value => obj => obj[field] === value
const isActive = byField('active')(true)
const isAdmin = byField('role')('admin')
const users = [
{ name: 'Иван', active: true, role: 'user' },
{ name: 'Анна', active: true, role: 'admin' },
{ name: 'Олег', active: false, role: 'user' },
]
console.log(users.filter(isActive).map(u => u.name)) // ['Иван', 'Анна']
console.log(users.filter(isAdmin).map(u => u.name)) // ['Анна']
// 3. Каррированный логгер для разных компонентов
const createLogger = level => context => message =>
console.log(`[${level}] [${context}] ${message}`)
const info = createLogger('INFO')
const error = createLogger('ERROR')
const authLog = info('Auth')
const orderLog = info('Orders')
const dbError = error('Database')
authLog('Пользователь вошёл') // [INFO] [Auth] Пользователь вошёл
orderLog('Заказ #101 создан') // [INFO] [Orders] Заказ #101 создан
dbError('Соединение потеряно') // [ERROR] [Database] Соединение потеряно
// 4. Универсальная curry()
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) return fn.apply(this, args)
return (...moreArgs) => curried(...args, ...moreArgs)
}
}
const clamp = curry((min, max, value) => Math.min(Math.max(value, min), max))
const clamp0to100 = clamp(0, 100)
console.log([50, -10, 200, 75].map(clamp0to100)) // [50, 0, 100, 75]В Stripe SDK форматирование суммы зависит от валюты, а валюта известна при инициализации — до того как суммы станут известны. Каррирование позволяет «заморозить» первый аргумент и получить специализированную функцию: formatRUB = formatCurrency('RUB'), затем formatRUB(1500). Это мощный паттерн для создания переиспользуемых специализированных функций.
Когда часть аргументов функции известна заранее, а часть — нет, каррирование позволяет зафиксировать известные и создать новую функцию только для оставшихся. Меньше повторений, более читаемый код.
// Обычная функция
function add(a, b) { return a + b }
add(3, 4) // 7
// Каррированная версия
const addC = a => b => a + b
addC(3)(4) // 7
// Создаём специализированные функции
const add5 = addC(5) // фиксируем первый аргумент
add5(10) // 15
add5(20) // 25const multiply = a => b => a * b
const double = multiply(2)
const triple = multiply(3)
double(7) // 14
triple(7) // 21
[1, 2, 3, 4, 5].map(double) // [2, 4, 6, 8, 10]
[1, 2, 3, 4, 5].map(triple) // [3, 6, 9, 12, 15]// Форматирование валюты — currency известна при инициализации
const formatCurrency = currency => amount =>
new Intl.NumberFormat('ru-RU', { style: 'currency', currency }).format(amount)
const formatRUB = formatCurrency('RUB')
const formatUSD = formatCurrency('USD')
formatRUB(1500) // '1 500,00 ₽'
formatUSD(29.99) // '29,99 $'
const prices = [1000, 2500, 750]
prices.map(formatRUB) // ['1 000,00 ₽', '2 500,00 ₽', '750,00 ₽']
// Логгер с preset-уровнем — уровень известен при настройке
const log = level => message => console.log(`[${level}] ${message}`)
const info = log('INFO')
const error = log('ERROR')
const warn = log('WARN')
info('Сервер запущен') // [INFO] Сервер запущен
error('Нет соединения') // [ERROR] Нет соединения
// Валидатор с конфигурацией
const minLength = min => str => str.length >= min
const maxLength = max => str => str.length <= max
const hasDigit = () => str => /\d/.test(str)
const isValidPassword = str =>
minLength(8)(str) && maxLength(32)(str) && hasDigit()(str)
isValidPassword('secret123') // true
isValidPassword('hi') // false — слишком короткий
isValidPassword('noDIGITS') // false — нет цифрыАвтоматически каррирует любую функцию с любым числом аргументов:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args) // аргументов достаточно — вызываем
}
return function(...moreArgs) {
return curried.apply(this, args.concat(moreArgs)) // накапливаем
}
}
}
const sum = curry((a, b, c) => a + b + c)
sum(1)(2)(3) // 6
sum(1, 2)(3) // 6 — можно группировать
sum(1)(2, 3) // 6
sum(1, 2, 3) // 6 — как обычная функцияf(a)(b)(c)bind или замыкание// Частичное применение через bind
const clamp = (min, max, value) => Math.min(Math.max(value, min), max)
const clamp0to100 = clamp.bind(null, 0, 100) // фиксируем min и max
clamp0to100(50) // 50
clamp0to100(-10) // 0
clamp0to100(200) // 100Ошибка 1: путаница с порядком аргументов
// Неудачный порядок — конфигурация в конце, данные первыми
const replace = (str, pattern, replacement) =>
str.replace(pattern, replacement)
// Не каррируется удобно — str меняется, а pattern обычно фиксирован
// Удачный порядок — конфигурация первая, данные последними
const replace2 = pattern => replacement => str =>
str.replace(pattern, replacement)
const removeTags = replace2(/<[^>]+>/g)('') // фиксируем конфигурацию
removeTags('<b>Hello</b>') // 'Hello'
removeTags('<p>World</p>') // 'World'Ошибка 2: каррирование функций с rest-аргументами
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) return fn.apply(this, args)
return (...more) => curried(...args, ...more)
}
}
// fn.length не работает для rest-параметров
const variadic = (...args) => args.reduce((a, b) => a + b, 0)
console.log(variadic.length) // 0 — fn.length игнорирует rest!
// Для вариадических функций используй ручное каррированиеcreateCharge(currency)(amount)(description)connect(mapState)(mapDispatch)(Component) (Redux)log(level)(context)(message)Каррированные функции для интернет-магазина: форматирование цен, фильтрация, логирование
// 1. Форматирование цен с конфигурацией
const formatCurrency = currency => amount =>
new Intl.NumberFormat('ru-RU', { style: 'currency', currency }).format(amount)
const formatRUB = formatCurrency('RUB')
const formatUSD = formatCurrency('USD')
const prices = [1000, 2500, 750]
console.log(prices.map(formatRUB))
// ['1 000,00 ₽', '2 500,00 ₽', '750,00 ₽']
console.log(formatUSD(29.99)) // '29,99 $'
// 2. Каррированный фильтр по полю объекта
const byField = field => value => obj => obj[field] === value
const isActive = byField('active')(true)
const isAdmin = byField('role')('admin')
const users = [
{ name: 'Иван', active: true, role: 'user' },
{ name: 'Анна', active: true, role: 'admin' },
{ name: 'Олег', active: false, role: 'user' },
]
console.log(users.filter(isActive).map(u => u.name)) // ['Иван', 'Анна']
console.log(users.filter(isAdmin).map(u => u.name)) // ['Анна']
// 3. Каррированный логгер для разных компонентов
const createLogger = level => context => message =>
console.log(`[${level}] [${context}] ${message}`)
const info = createLogger('INFO')
const error = createLogger('ERROR')
const authLog = info('Auth')
const orderLog = info('Orders')
const dbError = error('Database')
authLog('Пользователь вошёл') // [INFO] [Auth] Пользователь вошёл
orderLog('Заказ #101 создан') // [INFO] [Orders] Заказ #101 создан
dbError('Соединение потеряно') // [ERROR] [Database] Соединение потеряно
// 4. Универсальная curry()
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) return fn.apply(this, args)
return (...moreArgs) => curried(...args, ...moreArgs)
}
}
const clamp = curry((min, max, value) => Math.min(Math.max(value, min), max))
const clamp0to100 = clamp(0, 100)
console.log([50, -10, 200, 75].map(clamp0to100)) // [50, 0, 100, 75]Реализуй универсальную функцию curry(fn), которая каррирует функцию с любым числом аргументов. Используй её для создания набора утилит: каррированный clamp(min, max, value), pipe для двух функций и валидатор длины строки.
fn.apply(this, args) — вызов функции с накопленными аргументами. args.concat(moreArgs) — объединяем старые и новые аргументы. fn.length — количество параметров (не работает для rest-параметров). clamp(0)(100)(value) или clamp(0, 100)(value) — оба варианта должны работать.