Функциональное программирование (FP) — это стиль, при котором программа строится из чистых функций без побочных эффектов, данные не мутируются, а функции — объекты первого класса. В JS FP нативен: функции можно передавать как аргументы, возвращать из функций, хранить в переменных. Ключевые инструменты: map/filter/reduce, composе/pipe, каррирование, частичное применение.
Чистая функция — детерминирована (одинаковый ввод = одинаковый вывод) и не имеет побочных эффектов:
// НЕ чистая — читает внешнее состояние, имеет побочный эффект
let tax = 0.2
function calculatePrice(price) {
console.log('Расчёт...') // побочный эффект
return price * (1 + tax) // зависит от внешней переменной
}
// Чистая — предсказуема, изолирована
function calculatePrice(price, taxRate) {
return price * (1 + taxRate)
}
// calculatePrice(100, 0.2) всегда вернёт 120Не мутируй входные данные — возвращай новые:
// ПЛОХО — мутация аргумента
function addUser(users, user) {
users.push(user) // мутируем оригинальный массив!
return users
}
// ХОРОШО — возвращаем новый массив
function addUser(users, user) {
return [...users, user]
}
// Обновление объекта без мутации
function updateAge(user, newAge) {
return { ...user, age: newAge }
}Функция, принимающая или возвращающая другую функцию:
// map, filter, reduce — встроенные функции высшего порядка
const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map(x => x * 2) // [2, 4, 6, 8, 10]
const evens = numbers.filter(x => x % 2 === 0) // [2, 4]
const sum = numbers.reduce((acc, x) => acc + x, 0) // 15
// Собственная функция высшего порядка
function repeat(n, fn) {
return Array.from({ length: n }, (_, i) => fn(i))
}
repeat(3, i => i * i) // [0, 1, 4]// compose: выполняет функции справа налево (математическая нотация)
// compose(f, g, h)(x) === f(g(h(x)))
function compose(...fns) {
return (x) => fns.reduceRight((acc, fn) => fn(acc), x)
}
// pipe: выполняет функции слева направо (читается как поток данных)
// pipe(f, g, h)(x) === h(g(f(x)))
function pipe(...fns) {
return (x) => fns.reduce((acc, fn) => fn(acc), x)
}
// Пример использования
const trim = s => s.trim()
const toLowerCase = s => s.toLowerCase()
const addExclamation = s => s + '!'
const process = pipe(trim, toLowerCase, addExclamation)
process(' Hello World ') // 'hello world!'// Каррирование: функция с N аргументами → цепочка функций с 1 аргументом
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args)
}
return function(...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
const add = curry((a, b, c) => a + b + c)
add(1)(2)(3) // 6
add(1, 2)(3) // 6
add(1)(2, 3) // 6
// Частичное применение: фиксируем часть аргументов
function partial(fn, ...presetArgs) {
return function(...laterArgs) {
return fn(...presetArgs, ...laterArgs)
}
}
const multiply = (a, b) => a * b
const double = partial(multiply, 2)
double(5) // 10
double(10) // 20const orders = [
{ id: 1, amount: 150, status: 'completed', userId: 'u1' },
{ id: 2, amount: 80, status: 'pending', userId: 'u2' },
{ id: 3, amount: 300, status: 'completed', userId: 'u1' },
{ id: 4, amount: 50, status: 'cancelled', userId: 'u3' },
]
// Императивно: получить сумму завершённых заказов пользователя u1
function getTotalImperative(orders, userId) {
let total = 0
for (let i = 0; i < orders.length; i++) {
if (orders[i].userId === userId && orders[i].status === 'completed') {
total += orders[i].amount
}
}
return total
}
// Функционально: декларативно, читается как описание задачи
const getTotalFunctional = (orders, userId) =>
orders
.filter(o => o.userId === userId && o.status === 'completed')
.map(o => o.amount)
.reduce((sum, amount) => sum + amount, 0)
console.log(getTotalImperative(orders, 'u1')) // 450
console.log(getTotalFunctional(orders, 'u1')) // 450Чистые функции тестируются без моков и setup:
// Нечистая: требует mocking базы данных
async function getUser(id) {
return await db.query('SELECT * FROM users WHERE id = ?', [id])
}
// Чистая логика легко тестируется
function formatUser(rawUser) {
return {
id: rawUser.id,
name: rawUser.first_name + ' ' + rawUser.last_name,
email: rawUser.email.toLowerCase()
}
}
// test: formatUser({ id: 1, first_name: 'Ivan', last_name: 'Ivanov', email: 'Ivan@Example.COM' })
// ожидаем: { id: 1, name: 'Ivan Ivanov', email: 'ivan@example.com' }Начни с определений: «чистая функция», «иммутабельность», «функции первого класса». Покажи compose/pipe — это любимый вопрос на JS-собеседованиях. Объясни разницу между каррированием (f(a)(b)(c)) и частичным применением (partial(f, a)(b, c)). Подчеркни практическую ценность: FP-код легче тестировать, переиспользовать и рассуждать о нём.
Compose, pipe, partial application, curry и рефакторинг императивного кода в функциональный стиль
// ===== COMPOSE И PIPE =====
console.log('=== compose / pipe ===')
function compose(...fns) {
return (x) => fns.reduceRight((acc, fn) => fn(acc), x)
}
function pipe(...fns) {
return (x) => fns.reduce((acc, fn) => fn(acc), x)
}
// Строковые трансформации
const trim = s => s.trim()
const toLowerCase = s => s.toLowerCase()
const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1)
const addDot = s => s.endsWith('.') ? s : s + '.'
// pipe читается слева направо как поток
const normalizeText = pipe(trim, toLowerCase, capitalize, addDot)
console.log(normalizeText(' привет МИР ')) // 'Привет мир.'
console.log(normalizeText('HELLO')) // 'Hello.'
// compose читается справа налево (математически)
const normalizeCompose = compose(addDot, capitalize, toLowerCase, trim)
console.log(normalizeCompose(' HELLO ')) // 'Hello.'
// ===== ЧАСТИЧНОЕ ПРИМЕНЕНИЕ =====
console.log('\n=== Частичное применение ===')
function partial(fn, ...presetArgs) {
return function(...laterArgs) {
return fn(...presetArgs, ...laterArgs)
}
}
// Базовые функции
const multiply = (a, b) => a * b
const add = (a, b) => a + b
const pow = (base, exp) => Math.pow(base, exp)
// Специализированные функции через partial
const double = partial(multiply, 2)
const triple = partial(multiply, 3)
const add10 = partial(add, 10)
const square = partial(pow, undefined) // не подходит — нужен другой подход
console.log(double(5)) // 10
console.log(triple(5)) // 15
console.log(add10(25)) // 35
// Практичный пример: partial для fetch
function fetchData(baseUrl, endpoint) {
return `GET ${baseUrl}${endpoint}` // упрощённо
}
const fetchFromAPI = partial(fetchData, 'https://api.example.com')
console.log(fetchFromAPI('/users')) // GET https://api.example.com/users
console.log(fetchFromAPI('/products')) // GET https://api.example.com/products
// ===== КАРРИРОВАНИЕ =====
console.log('\n=== Каррирование ===')
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args)
}
return function(...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
const curriedAdd = curry((a, b, c) => a + b + c)
console.log(curriedAdd(1)(2)(3)) // 6
console.log(curriedAdd(1, 2)(3)) // 6
console.log(curriedAdd(1)(2, 3)) // 6
console.log(curriedAdd(1, 2, 3)) // 6
// Практичный пример: каррированный filter
const curriedFilter = curry((predicate, arr) => arr.filter(predicate))
const isEven = x => x % 2 === 0
const isPositive = x => x > 0
const filterEvens = curriedFilter(isEven)
const filterPositive = curriedFilter(isPositive)
console.log(filterEvens([1, 2, 3, 4, 5])) // [2, 4]
console.log(filterPositive([-2, -1, 0, 1, 2])) // [1, 2]
// ===== ИМПЕРАТИВНЫЙ VS ФУНКЦИОНАЛЬНЫЙ =====
console.log('\n=== Рефакторинг: императивный → функциональный ===')
const employees = [
{ name: 'Алиса', dept: 'engineering', salary: 120000, senior: true },
{ name: 'Боб', dept: 'marketing', salary: 80000, senior: false },
{ name: 'Карла', dept: 'engineering', salary: 150000, senior: true },
{ name: 'Денис', dept: 'engineering', salary: 95000, senior: false },
{ name: 'Ева', dept: 'marketing', salary: 90000, senior: true },
]
// Императивно: средняя зарплата senior-инженеров
function avgSeniorEngineerSalaryImperative(employees) {
let total = 0
let count = 0
for (let i = 0; i < employees.length; i++) {
const e = employees[i]
if (e.dept === 'engineering' && e.senior) {
total += e.salary
count++
}
}
return count > 0 ? total / count : 0
}
// Функционально: декларативно, без промежуточных переменных
const avgSeniorEngineerSalaryFP = (employees) => {
const salaries = employees
.filter(e => e.dept === 'engineering' && e.senior)
.map(e => e.salary)
return salaries.length > 0
? salaries.reduce((sum, s) => sum + s, 0) / salaries.length
: 0
}
const imp = avgSeniorEngineerSalaryImperative(employees)
const fp = avgSeniorEngineerSalaryFP(employees)
console.log('Императивно:', imp) // 135000
console.log('Функционально:', fp) // 135000Функциональное программирование (FP) — это стиль, при котором программа строится из чистых функций без побочных эффектов, данные не мутируются, а функции — объекты первого класса. В JS FP нативен: функции можно передавать как аргументы, возвращать из функций, хранить в переменных. Ключевые инструменты: map/filter/reduce, composе/pipe, каррирование, частичное применение.
Чистая функция — детерминирована (одинаковый ввод = одинаковый вывод) и не имеет побочных эффектов:
// НЕ чистая — читает внешнее состояние, имеет побочный эффект
let tax = 0.2
function calculatePrice(price) {
console.log('Расчёт...') // побочный эффект
return price * (1 + tax) // зависит от внешней переменной
}
// Чистая — предсказуема, изолирована
function calculatePrice(price, taxRate) {
return price * (1 + taxRate)
}
// calculatePrice(100, 0.2) всегда вернёт 120Не мутируй входные данные — возвращай новые:
// ПЛОХО — мутация аргумента
function addUser(users, user) {
users.push(user) // мутируем оригинальный массив!
return users
}
// ХОРОШО — возвращаем новый массив
function addUser(users, user) {
return [...users, user]
}
// Обновление объекта без мутации
function updateAge(user, newAge) {
return { ...user, age: newAge }
}Функция, принимающая или возвращающая другую функцию:
// map, filter, reduce — встроенные функции высшего порядка
const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map(x => x * 2) // [2, 4, 6, 8, 10]
const evens = numbers.filter(x => x % 2 === 0) // [2, 4]
const sum = numbers.reduce((acc, x) => acc + x, 0) // 15
// Собственная функция высшего порядка
function repeat(n, fn) {
return Array.from({ length: n }, (_, i) => fn(i))
}
repeat(3, i => i * i) // [0, 1, 4]// compose: выполняет функции справа налево (математическая нотация)
// compose(f, g, h)(x) === f(g(h(x)))
function compose(...fns) {
return (x) => fns.reduceRight((acc, fn) => fn(acc), x)
}
// pipe: выполняет функции слева направо (читается как поток данных)
// pipe(f, g, h)(x) === h(g(f(x)))
function pipe(...fns) {
return (x) => fns.reduce((acc, fn) => fn(acc), x)
}
// Пример использования
const trim = s => s.trim()
const toLowerCase = s => s.toLowerCase()
const addExclamation = s => s + '!'
const process = pipe(trim, toLowerCase, addExclamation)
process(' Hello World ') // 'hello world!'// Каррирование: функция с N аргументами → цепочка функций с 1 аргументом
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args)
}
return function(...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
const add = curry((a, b, c) => a + b + c)
add(1)(2)(3) // 6
add(1, 2)(3) // 6
add(1)(2, 3) // 6
// Частичное применение: фиксируем часть аргументов
function partial(fn, ...presetArgs) {
return function(...laterArgs) {
return fn(...presetArgs, ...laterArgs)
}
}
const multiply = (a, b) => a * b
const double = partial(multiply, 2)
double(5) // 10
double(10) // 20const orders = [
{ id: 1, amount: 150, status: 'completed', userId: 'u1' },
{ id: 2, amount: 80, status: 'pending', userId: 'u2' },
{ id: 3, amount: 300, status: 'completed', userId: 'u1' },
{ id: 4, amount: 50, status: 'cancelled', userId: 'u3' },
]
// Императивно: получить сумму завершённых заказов пользователя u1
function getTotalImperative(orders, userId) {
let total = 0
for (let i = 0; i < orders.length; i++) {
if (orders[i].userId === userId && orders[i].status === 'completed') {
total += orders[i].amount
}
}
return total
}
// Функционально: декларативно, читается как описание задачи
const getTotalFunctional = (orders, userId) =>
orders
.filter(o => o.userId === userId && o.status === 'completed')
.map(o => o.amount)
.reduce((sum, amount) => sum + amount, 0)
console.log(getTotalImperative(orders, 'u1')) // 450
console.log(getTotalFunctional(orders, 'u1')) // 450Чистые функции тестируются без моков и setup:
// Нечистая: требует mocking базы данных
async function getUser(id) {
return await db.query('SELECT * FROM users WHERE id = ?', [id])
}
// Чистая логика легко тестируется
function formatUser(rawUser) {
return {
id: rawUser.id,
name: rawUser.first_name + ' ' + rawUser.last_name,
email: rawUser.email.toLowerCase()
}
}
// test: formatUser({ id: 1, first_name: 'Ivan', last_name: 'Ivanov', email: 'Ivan@Example.COM' })
// ожидаем: { id: 1, name: 'Ivan Ivanov', email: 'ivan@example.com' }Начни с определений: «чистая функция», «иммутабельность», «функции первого класса». Покажи compose/pipe — это любимый вопрос на JS-собеседованиях. Объясни разницу между каррированием (f(a)(b)(c)) и частичным применением (partial(f, a)(b, c)). Подчеркни практическую ценность: FP-код легче тестировать, переиспользовать и рассуждать о нём.
Compose, pipe, partial application, curry и рефакторинг императивного кода в функциональный стиль
// ===== COMPOSE И PIPE =====
console.log('=== compose / pipe ===')
function compose(...fns) {
return (x) => fns.reduceRight((acc, fn) => fn(acc), x)
}
function pipe(...fns) {
return (x) => fns.reduce((acc, fn) => fn(acc), x)
}
// Строковые трансформации
const trim = s => s.trim()
const toLowerCase = s => s.toLowerCase()
const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1)
const addDot = s => s.endsWith('.') ? s : s + '.'
// pipe читается слева направо как поток
const normalizeText = pipe(trim, toLowerCase, capitalize, addDot)
console.log(normalizeText(' привет МИР ')) // 'Привет мир.'
console.log(normalizeText('HELLO')) // 'Hello.'
// compose читается справа налево (математически)
const normalizeCompose = compose(addDot, capitalize, toLowerCase, trim)
console.log(normalizeCompose(' HELLO ')) // 'Hello.'
// ===== ЧАСТИЧНОЕ ПРИМЕНЕНИЕ =====
console.log('\n=== Частичное применение ===')
function partial(fn, ...presetArgs) {
return function(...laterArgs) {
return fn(...presetArgs, ...laterArgs)
}
}
// Базовые функции
const multiply = (a, b) => a * b
const add = (a, b) => a + b
const pow = (base, exp) => Math.pow(base, exp)
// Специализированные функции через partial
const double = partial(multiply, 2)
const triple = partial(multiply, 3)
const add10 = partial(add, 10)
const square = partial(pow, undefined) // не подходит — нужен другой подход
console.log(double(5)) // 10
console.log(triple(5)) // 15
console.log(add10(25)) // 35
// Практичный пример: partial для fetch
function fetchData(baseUrl, endpoint) {
return `GET ${baseUrl}${endpoint}` // упрощённо
}
const fetchFromAPI = partial(fetchData, 'https://api.example.com')
console.log(fetchFromAPI('/users')) // GET https://api.example.com/users
console.log(fetchFromAPI('/products')) // GET https://api.example.com/products
// ===== КАРРИРОВАНИЕ =====
console.log('\n=== Каррирование ===')
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args)
}
return function(...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
const curriedAdd = curry((a, b, c) => a + b + c)
console.log(curriedAdd(1)(2)(3)) // 6
console.log(curriedAdd(1, 2)(3)) // 6
console.log(curriedAdd(1)(2, 3)) // 6
console.log(curriedAdd(1, 2, 3)) // 6
// Практичный пример: каррированный filter
const curriedFilter = curry((predicate, arr) => arr.filter(predicate))
const isEven = x => x % 2 === 0
const isPositive = x => x > 0
const filterEvens = curriedFilter(isEven)
const filterPositive = curriedFilter(isPositive)
console.log(filterEvens([1, 2, 3, 4, 5])) // [2, 4]
console.log(filterPositive([-2, -1, 0, 1, 2])) // [1, 2]
// ===== ИМПЕРАТИВНЫЙ VS ФУНКЦИОНАЛЬНЫЙ =====
console.log('\n=== Рефакторинг: императивный → функциональный ===')
const employees = [
{ name: 'Алиса', dept: 'engineering', salary: 120000, senior: true },
{ name: 'Боб', dept: 'marketing', salary: 80000, senior: false },
{ name: 'Карла', dept: 'engineering', salary: 150000, senior: true },
{ name: 'Денис', dept: 'engineering', salary: 95000, senior: false },
{ name: 'Ева', dept: 'marketing', salary: 90000, senior: true },
]
// Императивно: средняя зарплата senior-инженеров
function avgSeniorEngineerSalaryImperative(employees) {
let total = 0
let count = 0
for (let i = 0; i < employees.length; i++) {
const e = employees[i]
if (e.dept === 'engineering' && e.senior) {
total += e.salary
count++
}
}
return count > 0 ? total / count : 0
}
// Функционально: декларативно, без промежуточных переменных
const avgSeniorEngineerSalaryFP = (employees) => {
const salaries = employees
.filter(e => e.dept === 'engineering' && e.senior)
.map(e => e.salary)
return salaries.length > 0
? salaries.reduce((sum, s) => sum + s, 0) / salaries.length
: 0
}
const imp = avgSeniorEngineerSalaryImperative(employees)
const fp = avgSeniorEngineerSalaryFP(employees)
console.log('Императивно:', imp) // 135000
console.log('Функционально:', fp) // 135000Реализуй функции pipe(...fns), partial(fn, ...args) и refactorToFP(orders) — рефакторинг императивного кода. pipe должен последовательно применять функции слева направо. partial должен частично применять аргументы. refactorToFP должен вернуть массив имён пользователей, чьи заказы на сумму более 100, отсортированных по алфавиту, без дублей.
pipe: используй fns.reduce((acc, fn) => fn(acc), x). partial: возвращай функцию, которая вызывает fn(...presetArgs, ...laterArgs). refactorToFP: цепочка .filter().map().filter((v, i, arr) => arr.indexOf(v) === i).sort()
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке