1

Я люблю, что ECMAScript 6 позволяет писать кэрри функции, как это:Функция приложения для кэрри функций в JavaScript и ES6

var add = x => y => z => x + y + z; 

Однако, я ненавижу то, что нам нужно в скобки каждый аргумент каррированной функции:

add(2)(3)(5); 

Я хочу, чтобы иметь возможность применить кэрри функции к нескольким аргументам сразу:

add(2, 3, 5); 

Что мне делать? Меня не волнует производительность.

ответ

4

Каррирование и нанесение курри 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 выделки подробно обсуждается.

+0

_В этом случае нерегулярные функции могут применяться только один раз с неограниченным количеством аргументов. Любые последующие вызовы должны быть сделаны унарными. Это не очень идиоматично. Вы не поняли смысла. Если вы собираетесь писать '$ (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)'. Ваши встречные примеры тривиально неубедительны. Есть ли какие-либо серьезные встречные примеры? –

+0

Моя '' 'функция не волшебна. Это мясо, которое нужно использовать последовательно. Вы не можете смешивать '$' с обычным функциональным приложением и ожидать, что все будет работать. Запись '$ (sum, 1) (2, 3)' подобна смешиванию масла и воды. Либо используйте '$', либо используйте приложение с нормальной функцией, но не оба. Моей мотивацией для написания '' 'была простота и скорость. Функция '$' проще, чем 'curry', и быстрее, чем ваши нерешительные реализации. Все, что вам нужно сделать, это использовать его последовательно, и вы сможете писать функциональные программы на JavaScript с использованием синтаксиса Lispy со всеми преимуществами, которые может предложить карри. –

+0

@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

2

Большинство людей пишут кэрри функции, как это:

var add = curry(function (x, y, z) { 
    return x + y + z; 
}); 

add(2, 3, 5); 

В основном потому, что они не хотят, чтобы написать это:

var add = function (x) { 
    return function (y) { 
     return function (z) { 
      return x + y + z; 
     }; 
    }; 
}; 

add(2)(3)(5); 

nobody Однако agreesonhowtoimplementcurry.

Standards

Затем ECMAScript 6 решена первая проблема для нас:

var add = x => y => z => x + y + z; 

Но нам еще предстоит решить вторую проблему сами:

add(2)(3)(5); 

Это время для того, мы решаем эту проблему:

var $ = (func, ...args) => args.reduce((f, x) => f(x), func); 

Я надеюсь, что вы, как Лисп синтаксис:

$(add, 2, 3, 5); 

Sorry JQuery. Функциональное приложение является более фундаментальным.


Кроме того, Берги-х solution является удивительным:

const uncurry = func => (...args) => { 
    var result = func; 
    for (let arg of args) 
     result = result(arg); 
    return result; 
} 

var add = uncurry(x => y => z => x + y + z); 

add(2, 3, 5); 

Однако я по-прежнему предпочитают использовать $.

+1

Теперь, если бы вы могли сделать меня в стиле Haskell 'add $ (2, 3, 5)' Я был бы счастлив :-) – Bergi

+0

'arguments' в ES6, серьезно? – Bergi

+0

@Bergi Модернизировал мое решение для ES6. –

2

Вы можете легко написать функцию, которая применяется несколько аргументов для такой каррированной функции:

const apply = fn => (...args) => args.reduce((f, x) => f(x), fn); 

// or alternatively: 
const apply = fn => (...args) => { 
    let f = fn; 
    for (const x of args) f = f(x); 
    return f; 
} 

Теперь вы можете вызвать add так:

apply(add)(2, 3, 4) 

, и если вы до сих пор ненавижу, что вы могли бы также использовать

const $ = apply(apply); 

$(add, 2, 3, 4) 
+0

Мне нравится, где вы собираетесь с этим. –

+0

'$ = применять (применять)' работает для меня. –

+1

Было бы разумно переименовать 'apply' в' uncurry'. Он также работает в Haskell. См. Типы. –

0

Учитывая это хороший ответ от Берги

const apply = fn => (...args) => args.reduce((f, x) => f(x), fn); 

Это не сразу видно, что это так же, как

const apply = fn => (...args) => args.reduce(uncurry(id), fn); 

Я обеспечиваю этот ответ только как понимание. apply еще более упрощается, как только у вас есть общие для reduce

const id = x => x; 
const uncurry = f => (x,y) => f(x)(y); 
const reduce = f => y => xs => xs.reduce(uncurry(f), y); 

// fresh ! 
const apply = f => (...xs) => reduce(id)(f)(xs); 

Я думаю, что это ценно, чтобы быть в состоянии видеть apply написано в этой упрощенной форме, даже если это не в конечном итоге окончательных реализаций.

Вдохновленный после просмотра Берги есть элегантная $, я хотел бы сделать одно последнее изменение в моей реализации

const apply = reduce(id); 
const $ = (f, ...xs) => apply(f)(xs); 

Теперь он используется как этот

// given 
let add = x => y => z => x + y + z; 

// with an existing list of inputs 
let nums = [1,2,3]; 
apply(add)(nums); // 6 

// arbitrary inputs 
$(add, 1, 2, 3); // 6 

Я не заботится о производительности.

Хорошо, хорошо! Эта реализация очень хорошо читается, но она использует больше вызовов функций, поэтому будет выполнять медленнее.

+0

Это очень интересно! Как вы думаете, что 'apply (add3) (1) (2, 3)' на самом деле не проблема? Сложно ли мне это? – rand

+0

По крайней мере, я ожидал бы, что 'uncurry (add3) (1)' будет эквивалентом 'add3 (1, undefined, undefined)'. В конце концов, мы unucurreed _add3_, поэтому он больше не должен действовать как карри-функция, верно? Короче говоря, я просто не думаю, что интуитивно или правильно поддерживать 'apply (add3) (1) (2,3)' так, как вы хотите, чтобы он работал. То есть, я ожидал бы, что 'add3' будет выглядеть как' add3 = x => (y, z) => ... ', как вы его называете; который может быть вполне допустимым определением функции btw. – naomik

+0

Опасность автоматического разворачивания возвращаемого значения, если это может быть функция, может быть просто неверной в некоторых случаях. Я могу частично применить функцию curried, а затем применить другие части позже. 'let f = apply (add3) (1); применить (е) (2,3) '. Или рассмотрите такие функции, как 'compose', которые предназначены для возврата функции. Было бы несправедливо подвергать свою продукцию автоматическому сбору, если мы этого не хотели. – naomik

Смежные вопросы