ФП: функции высшего порядка, compose и pipe

↑ 8.1 Парадигмы: ООП, ФП, SOLID · ← Предыдущая · → Следующая

Желательно~3 мин чтенияУровень: senior

Зачем это на собесе

ФП-подход лежит в основе Vue composition API, Redux, RxJS; просят реализовать compose/pipe/curry.

Ключевые идеи

  • чистые функции: результат зависит только от аргументов, без побочных эффектов;
  • иммутабельность: новое значение вместо изменения старого;
  • функции — значения: передаём, возвращаем, композируем;
  • функции высшего порядка (HOF): принимают или возвращают функции.

HOF

const withLogging = <A extends unknown[], R>(fn: (...a: A) => R) =>
  (...args: A): R => { console.log('call', fn.name, args); return fn(...args) }
 
const debounce = <A extends unknown[]>(fn: (...a: A) => void, ms: number) => {
  let t: ReturnType<typeof setTimeout>
  return (...args: A) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms) }
}

map, filter, reduce, sort, Array.from — тоже HOF.

pipe и compose

const pipe = <T>(...fns: Array<(x: T) => T>) => (x: T) => fns.reduce((acc, fn) => fn(acc), x)
const compose = <T>(...fns: Array<(x: T) => T>) => (x: T) => fns.reduceRight((acc, fn) => fn(acc), x)
 
const slugify = pipe<string>(
  s => s.trim(),
  s => s.toLowerCase(),
  s => s.replace(/\s+/g, '-'),
)
slugify('  Hello World ')   // 'hello-world'

pipe читается слева направо, compose — справа налево.

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

const curry = (fn: Function) => function c(...a: any[]): any {
  return a.length >= fn.length ? fn(...a) : (...b: any[]) => c(...a, ...b)
}
const add = curry((a: number, b: number, c: number) => a + b + c)
add(1)(2)(3); add(1, 2)(3)

Иммутабельность

const next = { ...state, user: { ...state.user, name: 'Аня' } }
const list2 = [...list, item]
const list3 = list.filter(x => x.id !== id)
structuredClone(obj)   // глубокая копия

Библиотеки: Immer (мутирующий синтаксис поверх неизменяемости).

Во фронтенде

Редьюсеры, селекторы, computed, чистые преобразования данных, RxJS-операторы, мемоизация.

Вопросы с ответами