Каррирование и нанесение курри ed являются противоречивыми проблемами в Javascript. Проще говоря, есть два противоположных взгляда, которые я кратко иллюстрирую.
- Использование отдельной функции Карри только при необходимости
адаптации понятий из других языков или парадигм, в принципе, хорошая вещь. Однако эта адаптация должна выполняться с помощью элементарных средств целевого языка. Что это значит для currying в javascript?
- выделанные функции называются как последовательность одинарных функций:
add3(1)(2)(3); // 6
- собственные функции вручную кэрри со стрелками
const add3 = x => y => z => x + y + z;
- функции третьей стороны или способы кэрри с помощью отдельной функции карри
- Использование отдельной реализации карри по умолчанию
Там проблема с предложенной функцией $
/uncurry
:
const $ = (func, ...args) => args.reduce((f, x) => f(x), func);
const sum = x => y => z => x + y + z;
$(sum, 1, 2, 3); // 6
$(sum, 1, 2)(3); // 6
$(sum, 1)(2, 3); // z => x + y + z
Таким образом uncurried функция может только один раз применяться с неограниченным числом аргументов. Любые последующие вызовы должны быть сделаны унарными. Функция выполняет именно то, что она обещает. Тем не менее, это не позволяет использовать функции в валюте, такие как JavaScript-разработчики. Большинство современных реализаций карри более гибкие. Вот реализация расширенная:
const uncurry = f => (...args) => args.reduce(
(g, x) => (g = g(x), typeof g === "function" && g.length === 1
? uncurry(g)
: g), f
);
const sum = uncurry(x => y => z => x + y + z);
sum(1, 2, 3); // 6
sum(1, 2)(3); // 6
sum(1)(2, 3); // 6
Эта реализация работает, если вы, как авто-uncurrying: После того, как сама uncurried функция производит каррированную функцию в качестве возвращаемого значения, эта функция возвращается автоматически uncurried. Если вы предпочитаете больше контроля, более подходящей может быть следующая реализация.
Окончательный uncurry реализация
const partial = arity => f => function _(...args) {
return args.length < arity
? (...args_) => _(...args.concat(args_))
: f(args);
};
const uncurry = arity => f => partial(arity)(args => args.reduce((g, x) => g(x), f));
const sum = uncurry(3)(x => y => z => x + y + z);
sum(1, 2, 3); // 6
sum(1, 2)(3); // 6
sum(1)(2, 3); // 6
Этот крошечный параметр Арность приносит нам желаемый контроль. Я думаю, что это того стоит.
карри решение для отдыха
Что мы делаем с функциями, которые находятся вне нашего контроля и, следовательно, не были вручную кэрри?
const curryN = uncurry(2)(arity => f => partial(arity)(args => f(...args)));
const add = curryN(2, (x, y) => x + y);
const add2 = add(2);
add2(4); // 6
К счастью, мы смогли повторно partial
и держать curryN
кратким. С этим решением также могут быть выполнены вариационные функции или такие с дополнительными параметрами.
Бонус: «Funcualizing» и выделка Методы
снискать методы, мы должны преобразовать это неприятное, неявное this
свойства в явном параметре.Оказывается, что мы можем использовать partial
для адекватной реализации еще раз:
const apply = uncurry(2)(arity => key => {
return arity
? partial(arity + 1)(args => args[arity][key](...args.slice(0, arity)))
: o => o[key]();
});
apply(0, "toLowerCase")("A|B|C"); // "a|b|c"
apply(0, "toLowerCase", "A|B|C"); // "a|b|c"
apply(1, "split")("|")("A|B|C"); // ["A", "B", "C"]
apply(1, "split")("|", "A|B|C"); // ["A", "B", "C"]
apply(1, "split", "|", "A|B|C"); // ["A", "B", "C"]
apply(2, "includes")("A")(0)("A|B|C"); // true
apply(2, "includes", "A", 0, "A|B|C"); // true
В этом blog post выделки подробно обсуждается.
_В этом случае нерегулярные функции могут применяться только один раз с неограниченным количеством аргументов. Любые последующие вызовы должны быть сделаны унарными. Это не очень идиоматично. Вы не поняли смысла. Если вы собираетесь писать '$ (sum, 1, 2) (3)' или '$ (sum, 1) (2, 3)', то вы побеждаете цель иметь '$' вообще. Вы могли бы просто написать 'sum (1) (2) (3)'. Функция '' 'на самом деле идиоматична для программистов Lisp. Вместо '$ (sum, 1) (2, 3)' вы должны написать '$ ($ (sum, 1), 2, 3)' или даже просто '$ (sum, 1, 2, 3)'. Ваши встречные примеры тривиально неубедительны. Есть ли какие-либо серьезные встречные примеры? –
Моя '' 'функция не волшебна. Это мясо, которое нужно использовать последовательно. Вы не можете смешивать '$' с обычным функциональным приложением и ожидать, что все будет работать. Запись '$ (sum, 1) (2, 3)' подобна смешиванию масла и воды. Либо используйте '$', либо используйте приложение с нормальной функцией, но не оба. Моей мотивацией для написания '' 'была простота и скорость. Функция '$' проще, чем 'curry', и быстрее, чем ваши нерешительные реализации. Все, что вам нужно сделать, это использовать его последовательно, и вы сможете писать функциональные программы на JavaScript с использованием синтаксиса Lispy со всеми преимуществами, которые может предложить карри. –
@Aadit: Когда мы говорим о функциях с курсивом в Javascript, тогда многие люди связывают следующее приложение: 'f (x, y, z)', 'f (x) (y, z)', 'f (x , y) (z) ', ... - даже если это уже не применение карри-функций в математическом смысле. Ваша собственная реализация [curry] (http://stackoverflow.com/questions/27996544/how-to-correctly-curry-a-function-in-javascript) выполняется так. Так что совершенно законный вопрос, почему '' 'отказывается от такого поведения. Ваша реализация просто не соответствует моим потребностям, и я склоняюсь к синтаксису скобки. – rand