Как реализовать каррирование функции с сохранением контекста и аргументов
Что проверяют
Понимаете ли вы замыкания, this и свойство fn.length, а не только умеете написать a => b => c => a + b + c. Ключевые слова в вопросе «с сохранением контекста»: типичная реализация на стрелочных функциях теряет this, и интервьюер ждёт, заметите ли вы это. Следом обычно просят каррировать метод объекта.
Как отвечать
Каркас такой: curry(fn) возвращает обычную функцию curried, которая копит аргументы. Если их набралось не меньше fn.length, вызывается fn.apply(this, args). Если меньше, возвращается новая обычная функция, которая при вызове делает curried.apply(this, [...args, ...next]).
Объясните, почему именно так:
- обычные функции, не стрелки: у стрелки нет своего
this, она берёт его из места объявления, и метод объекта в строгом режиме получитundefinedвместо объекта; applyвместо прямого вызова: только так контекст вызова доезжает до исходной функции;- новый массив на каждом шаге: аргументы складываются через
[...args, ...next], а неpushв общий массив. Тогда частично применённую функцию можно переиспользовать:add5 = curried(5)даёт один и тот же результат сколько угодно раз; - арность:
fn.lengthне учитывает параметры со значением по умолчанию и rest-параметры, для(a, b = 1)он равен 1, для(...xs)равен 0. Поэтому хорошая реализация принимает арность вторым аргументом:curry(fn, arity = fn.length).
Отдельно проговорите, чей контекст считается правильным: this первого частичного вызова или последнего. В реализации выше побеждает последний. Если нужен зафиксированный, сделайте fn.bind(obj) до каррирования. Для сравнения можно упомянуть _.curry из lodash с плейсхолдерами, которые позволяют пропустить аргумент.
Где спотыкаются
Пишут всё на стрелках и проверяют только на add(a, b, c), где this не нужен. На методе вроде obj.sum такой код ломается.
Копят аргументы в одном массиве из замыкания curry. После первого полного вызова каррированная функция хранит старые аргументы и возвращает неверный результат.
Путают каррирование с частичным применением: bind(null, 1) фиксирует аргументы, но не превращает функцию в цепочку вызовов по одному.