Функциональное программирование
Чистые функции, каррирование, частичное применение, композиция, мемоизация, миксины и арность функций.
11 вопросов
JuniorТеорияЧастоЧто такое мемоизация, и когда она ускоряет функцию?
Что такое мемоизация, и когда она ускоряет функцию?
Мемоизация кэширует результат функции по её аргументам, поэтому повторный вызов с теми же входными данными возвращает сохранённое значение вместо пересчёта. Она помогает только чистым функциям, чей результат зависит лишь от аргументов, и окупается, когда одни и те же входы повторяются, а вычисление дорогое. Это обмен памяти на скорость, поэтому кэширование дешёвых или редко повторяемых вызовов просто тратит память.
Типичные ошибки
- ✗Мемоизировать нечистые функции, чей вывод зависит от внешнего состояния, возвращая устаревшие значения
- ✗Считать мемоизацию всегда выигрышем, игнорируя её стоимость в памяти
- ✗Кэшировать дешёвые или никогда не повторяемые вызовы, где накладные расходы перевешивают выгоду
Уточняющие вопросы
- →Почему мемоизируемая функция должна быть чистой, чтобы кэш оставался верным?
- →Чем неограниченный кэш мемоизации рискует утечкой памяти?
JuniorТеорияЧастоЧто такое чистая функция?
Что такое чистая функция?
Чистая функция возвращает один и тот же результат для одних и тех же входных данных и не имеет побочных эффектов — она не изменяет внешнее состояние, не делает ввод-вывод и не зависит ни от чего, кроме своих аргументов. Например, (a, b) => a + b чистая, а функция, добавляющая элемент во внешний массив или читающая Date.now(), нечистая. Чистота делает код предсказуемым и лёгким для тестирования.
Типичные ошибки
- ✗Думать, что функция, изменяющая свои аргументы или внешнюю переменную, всё ещё может быть чистой
- ✗Считать, что чистота — про производительность или кэширование, а не про детерминизм и отсутствие побочных эффектов
Уточняющие вопросы
- →Является ли чистой функция, вызывающая
Date.now(), и почему? - →Как чистота упрощает модульное тестирование функции?
JuniorТеорияЧастоЧем rest-параметры отличаются от объекта arguments?
Чем rest-параметры отличаются от объекта arguments?
Rest-параметр ...args собирает оставшиеся аргументы в настоящий Array, поэтому методы массива вроде map работают напрямую, и он содержит только параметры после именованных. Объект arguments похож на массив, но им не является, не имеет этих методов и всегда содержит все аргументы. У стрелочных функций нет arguments, поэтому rest — современный выбор.
Типичные ошибки
- ✗Считать, что
arguments— настоящий массив и поддерживает методы вродеmapилиfilter - ✗Забывать, что у стрелочных функций нет объекта
arguments, поэтому там работает только rest
Уточняющие вопросы
- →Как преобразовать объект
argumentsв настоящий массив? - →Почему у функции может быть только один rest-параметр и почему он должен быть последним?
MiddleТеорияИногдаЧто делают функциональные помощники compose и pipe?
Что делают функциональные помощники compose и pipe?
Оба объединяют несколько функций так, что выход одной подаётся на вход следующей, создавая одну новую функцию. Различаются только направлением: compose(f, g)(x) идёт справа налево как f(g(x)), а pipe(f, g)(x) — слева направо как g(f(x)). Они не встроены в язык и лучше всего работают с чистыми функциями одного аргумента.
Типичные ошибки
- ✗Путать направление —
composeидёт справа налево,pipe— слева направо - ✗Считать
composeиpipeвстроенными глобалями, а не помощниками, которые пишут или импортируют
Уточняющие вопросы
- →Почему составленные функции лучше работают, когда каждая чистая и унарная?
- →Как написать
pipeс помощьюreduce?
MiddleТеорияИногдаЧто такое каррирование?
Что такое каррирование?
Каррирование преобразует функцию многих аргументов в цепочку вложенных функций, каждая из которых принимает ровно один аргумент. Так (a, b, c) => a + b + c становится a => b => c => a + b + c, вызываемым как f(1)(2)(3). Каждый вызов захватывает свой аргумент в замыкание и возвращает следующую функцию, что даёт частичное применение и переиспользование.
Типичные ошибки
- ✗Путать каррирование с передачей всех аргументов разом в виде массива
- ✗Думать, что каррирование кэширует результаты как мемоизация, а не разбивает аргументы
Уточняющие вопросы
- →Какая возможность языка позволяет каррированной цепочке помнить ранние аргументы?
- →Как каррирование обеспечивает частичное применение функции?
MiddleТеорияИногдаКак реализовать мемоизацию через замыкание и кэш на Map?
Как реализовать мемоизацию через замыкание и кэш на Map?
Оберните функцию и держите Map в замыкании; при каждом вызове постройте ключ кэша из аргументов, верните кэшированное значение, если оно есть, иначе вычислите, сохраните и верните. Подвох в ключе: объекты и массивы превращаются в один и тот же [object Object], поэтому наивный ключ сталкивает разные входы, а JSON.stringify игнорирует порядок свойств и теряет функции. Map допускает нестроковые ключи, но сопоставляет объекты лишь по идентичности.
Типичные ошибки
- ✗Наивно ключевать объектные аргументы, из-за чего разные объекты сталкиваются как
[object Object] - ✗Считать, что
Mapсопоставляет объектные ключи по содержимому, а не по идентичности - ✗Доверять ключам из
JSON.stringifyнесмотря на порядок свойств и потерю функций
Уточняющие вопросы
- →Как построить стабильный ключ кэша для нескольких объектных аргументов?
- →Чем
WeakMap-кэш помогает не утекать памятью на объектных ключах?
MiddleТеорияИногдаЧто такое частичное применение, и чем оно отличается от каррирования?
Что такое частичное применение, и чем оно отличается от каррирования?
Частичное применение заранее фиксирует часть аргументов функции, создавая новую функцию, принимающую остальные; fn.bind(null, 1) предзаполняет первый аргумент. Каррирование же превращает n-арную функцию в цепочку функций по одному аргументу, каждая из которых возвращает следующую. Частичное применение задаёт сколько угодно аргументов разом и возвращает одну функцию, а каррирование всегда берёт по одному аргументу за вызов.
Типичные ошибки
- ✗Считать частичное применение и каррирование одним и тем же
- ✗Думать, что
bindсразу выполняет функцию, а не возвращает новую - ✗Полагать, что частичное применение задаёт ровно один аргумент за вызов
Уточняющие вопросы
- →Как
fn.bindзадаётthis, предзаполняя ведущие аргументы? - →Почему каррированную функцию можно вызывать по одному аргументу за раз?
JuniorТеорияРедкоЧто такое арность функции, и что возвращает fn.length?
Что такое арность функции, и что возвращает fn.length?
Арность — это число параметров, объявленных функцией. fn.length считает только параметры до первого со значением по умолчанию или до rest-параметра, поэтому (a, b) => {} даёт 2, а (a, b = 1, ...r) => {} даёт 1. Параметр — это именованный слот в объявлении; аргумент — фактическое значение, переданное при вызове. Функции без имени называются анонимными.
Типичные ошибки
- ✗Думать, что
fn.lengthсчитает аргументы при вызове, а не объявленные параметры - ✗Считать, что параметры со значением по умолчанию и rest входят в
fn.length - ✗Путать параметр и аргумент, хотя они называют разные вещи
Уточняющие вопросы
- →Как прочитать фактически переданные аргументы, если
fn.lengthигнорирует rest? - →Почему
arguments.lengthотличается отfn.lengthдля одного и того же вызова?
MiddleТеорияРедкоЧто такое примеси, и как они добавляют поведение в прототип?
Что такое примеси, и как они добавляют поведение в прототип?
Примесь — это объект переиспользуемых методов, скопированных в цель через Object.assign(Class.prototype, mixin), что позволяет несвязанным классам делить поведение без общего предка. В отличие от наследования с одной линейной цепочкой прототипов и связью is-a, примеси составляют несколько независимых способностей и выражают has-a. Цена в том, что скопированные методы молча перезаписывают одноимённые, и нет super, чтобы добраться до исходного.
Типичные ошибки
- ✗Думать, что примеси создают связь наследования
is-a, а не копируют методы - ✗Ожидать, что
superдоберётся до метода, перезаписанного примесью - ✗Забывать, что
Object.assignмолча перезаписывает одноимённые методы цели
Уточняющие вопросы
- →Почему порядок примесей важен, когда две примеси задают один метод?
- →Как фабрика функциональных примесей позволяет параметризовать копируемое поведение?
SeniorТеорияРедкоЧто означают ссылочная прозрачность, побочные эффекты и бесточечный стиль?
Что означают ссылочная прозрачность, побочные эффекты и бесточечный стиль?
Ссылочная прозрачность означает, что выражение можно заменить его значением без изменения программы — чистые функции прозрачны, поэтому add(2, 3) можно заменить на 5. Побочный эффект — это любое изменение состояния помимо возвращаемого значения: ввод-вывод, правки DOM, мутация входных данных, Math.random() или чтение часов. Бесточечный стиль составляет функции, не называя их аргументы, собирая поведение из мелких чистых частей.
Типичные ошибки
- ✗Называть нечистую функцию ссылочно прозрачной лишь потому, что она всё же возвращает значение
- ✗Думать, что мутация аргумента или чтение часов не является побочным эффектом
Уточняющие вопросы
- →Почему функция, вызывающая
Math.random(), не ссылочно прозрачна? - →Как каррирование и композиция делают бесточечный стиль практичным?
SeniorТеорияРедкоЧто такое thunk и бесточечный стиль, и когда мемоизация и частичное применение вредят?
Что такое thunk и бесточечный стиль, и когда мемоизация и частичное применение вредят?
Thunk — это функция без аргументов, оборачивающая отложенное вычисление, поэтому работа выполняется лишь при вызове — полезно для ленивого вычисления. Функциональная техника «бесточечный стиль» строит функции, составляя другие без называния аргументов. Мемоизация вредит, когда входы редко повторяются или плохо ключуются, наращивая неограниченный кэш без выгоды. Частичное применение вредит, когда скрывает порядок аргументов или фиксирует не тот ведущий слот, делая места вызова непонятными.
Типичные ошибки
- ✗Думать, что thunk выполняет работу сразу, а не откладывает до вызова
- ✗Считать мемоизацию безвредной даже когда входы редко повторяются
- ✗Полагать, что частичное применение всегда проясняет, а не запутывает места вызова
Уточняющие вопросы
- →Как thunk обеспечивает ленивое вычисление дорогого значения по умолчанию?
- →Когда неограниченный кэш мемоизации становится утечкой памяти в долгоживущем приложении?