JavaScript карри: каковы практические приложения?

172

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

Где вы используете каррирование в JavaScript (или где его используют основные библиотеки)? Приветствуются манипуляции с DOM или общие примеры разработки приложений.

В одном из ответов упоминается анимация. Такие функции, как slideUp, fadeInпринимают элемент в качестве аргументов и обычно являются карри-функцией, возвращающей функцию высокого порядка со встроенной «функцией анимации» по умолчанию. Почему это лучше, чем просто применять функцию более высокого уровня с некоторыми значениями по умолчанию?

Есть ли недостатки в его использовании?

Как и просили, вот несколько хороших ресурсов по каррированию JavaScript:

Я добавлю больше, как они появляются в комментариях.


Итак, согласно ответам, карри и частичное применение в целом являются удобными техниками.

Если вы часто «дорабатываете» высокоуровневую функцию, вызывая ее с одинаковой конфигурацией, вы можете каррировать (или использовать частичку Resig) высокоуровневую функцию для создания простых, лаконичных вспомогательных методов.

Дейв Нолан
источник
Вы можете добавить ссылку на ресурс, который описывает, что такое карринг JS? учебник или сообщение в блоге было бы здорово.
Эрик Шуновер
2
svendtofte.com давно существует, но если вы пропустите весь раздел из «Ускоренного курса в ML» и начнете снова с «Как писать JavaScript с карри», это станет отличным введением в каррирование в js.
Данио
1
Это хорошая отправная точка, чтобы понять, что такое карри и частичное применение: slid.es/gsklee/functional-programming-in-5-minutes
gsklee
1
Ссылка на svendtofte.comвыглядит мертвой - нашел ее на компьютере WayBack, хотя на web.archive.org/web/20130616230053/http://www.svendtofte.com/… Извините, blog.morrisjohns.com/javascript_closures_for_dummies, кажется, не работает тоже
phatskat
1
Кстати, частичная версия Resig неполноценна (конечно, не «на деньги») в том смысле, что она, скорее всего, потерпит неудачу, если одному из предварительно инициализированных («каррированных») аргументов будет присвоено значение undefined . Любой, кто интересуется хорошей функцией каррирования, должен получить оригинал из funcitonal.js Оливера Стила , поскольку у него нет такой проблемы.
RobG

Ответы:

35

@ Хэнк Гей

В ответ на комментарий EmbiggensTheMind:

Я не могу вспомнить случай, когда каррирование - само по себе - полезно в JavaScript; Это методика преобразования вызовов функций с несколькими аргументами в цепочки вызовов функций с одним аргументом для каждого вызова, но JavaScript поддерживает несколько аргументов в одном вызове функции.

В JavaScript - и я полагаю, что большинство других реальных языков (не лямбда-исчисление) - это обычно ассоциируется с частичным применением. Джон Резиг объясняет это лучше , но суть в том, что есть некоторая логика, которая будет применяться к двум или более аргументам, и вы знаете только значение (я) для некоторых из этих аргументов.

Вы можете использовать частичное приложение / каррирование, чтобы исправить эти известные значения и вернуть функцию, которая принимает только неизвестные, которая будет вызвана позже, когда у вас действительно есть значения, которые вы хотите передать. Это обеспечивает отличный способ избежать повторения, когда вы бы снова и снова вызывали одни и те же встроенные JavaScript-функции со всеми одинаковыми значениями, кроме одного. Чтобы украсть пример Джона:

String.prototype.csv = String.prototype.split.partial(/,\s*/);
var results = "John, Resig, Boston".csv();
alert( (results[1] == "Resig") + " The text values were split properly" );
Хэнк Гей
источник
2
Я попытался объяснить это с помощью некоторых примеров и демонстраций в моей письменной статье.
Захир Ахмед
6
Это действительно плохой ответ. Карри не имеет ничего общего с частичным применением. Карри включает функцию композиции. Композиция функций позволяет повторное использование функций. Повторное использование функций повышает удобство сопровождения кода. Это так просто!
2
@ftor сэр, вы очень плохой ответ. Очевидно, карри - это сделать функции вкуснее. Вы явно упустили суть.
Каллат
112

Вот интересное И практическое использование каррирования в JavaScript, который использует замыкания :

function converter(toUnit, factor, offset, input) {
    offset = offset || 0;
    return [((offset + input) * factor).toFixed(2), toUnit].join(" ");
}

var milesToKm = converter.curry('km', 1.60936, undefined);
var poundsToKg = converter.curry('kg', 0.45460, undefined);
var farenheitToCelsius = converter.curry('degrees C', 0.5556, -32);

milesToKm(10);            // returns "16.09 km"
poundsToKg(2.5);          // returns "1.14 kg"
farenheitToCelsius(98);   // returns "36.67 degrees C"

Это зависит от curryрасширения Function, хотя, как вы можете видеть, оно использует только apply(ничего особенного):

Function.prototype.curry = function() {
    if (arguments.length < 1) {
        return this; //nothing to curry with - return function
    }
    var __method = this;
    var args = toArray(arguments);
    return function() {
        return __method.apply(this, args.concat([].slice.apply(null, arguments)));
    }
}
Prisoner ZERO
источник
5
Это круто! Я вижу , что похоже на LISP цитату , которая говорит : «Лисп является программируемым язык программирования»
santiagobasulto
2
Интересно, но этот пример не работает. offset+inputбудет undefined + 1.60936в вашем milesToKmпримере; это приводит к NaN.
Натан Лонг
3
@Nathan - смещение не может быть неопределенным - по умолчанию оно
равно
6
Из того, что я прочитал (только сейчас), «карри» обычно не входит в пакет трюков Function, если вы не используете библиотеку Prototype или не добавляете ее самостоятельно. Очень круто, хотя.
Roboprog
11
То же самое можно сделать с помощью метода ES5 bind (). Bind создает новую функцию, которая при вызове вызывает исходную функцию с контекстом ее первого аргумента и с последующей последовательностью аргументов (предшествующей любой, переданной новой функции). Так что вы можете сделать ... var milesToKm = converter.bind (this, 'km', 1.60936); или var farenheitToCelsius = converter.bind (это, градусы C, 0,5556, -32); Первый аргумент, контекст, это, здесь не имеет значения, так что вы можете просто передать undefined. Конечно, вам нужно будет дополнить прототип базовой функции собственным методом связывания для не ES5-отката
hacklikecrack
7

Я обнаружил, что функции, похожие на python, functools.partialболее полезны в JavaScript:

function partial(fn) {
  return partialWithScope.apply(this,
    Array.prototype.concat.apply([fn, this],
      Array.prototype.slice.call(arguments, 1)));
}

function partialWithScope(fn, scope) {
  var args = Array.prototype.slice.call(arguments, 2);
  return function() {
    return fn.apply(scope, Array.prototype.concat.apply(args, arguments));
  };
}

Почему вы хотите использовать это? Типичная ситуация, когда вы хотите использовать это, когда вы хотите связать thisв функции значение:

var callback = partialWithScope(Object.function, obj);

Теперь, когда вызывается обратный вызов, thisуказывает наobj . Это полезно в ситуациях события или для экономии места, потому что обычно делает код короче.

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

Армин Ронахер
источник
7

Согласиться с Хэнком Гей - Это чрезвычайно полезно в некоторых настоящих функциональных языках программирования - потому что это необходимая часть. Например, в Haskell вы просто не можете передать несколько параметров функции - вы не можете сделать это в чисто функциональном программировании. Вы принимаете один параметр за раз и строите свою функцию. В JavaScript это просто не нужно, несмотря на надуманные примеры типа «конвертер». Вот тот же самый код конвертера, без необходимости каррирования:

var converter = function(ratio, symbol, input) {
    return (input*ratio).toFixed(2) + " " + symbol;
}

var kilosToPoundsRatio = 2.2;
var litersToUKPintsRatio = 1.75;
var litersToUSPintsRatio = 1.98;
var milesToKilometersRatio = 1.62;

converter(kilosToPoundsRatio, "lbs", 4); //8.80 lbs
converter(litersToUKPintsRatio, "imperial pints", 2.4); //4.20 imperial pints
converter(litersToUSPintsRatio, "US pints", 2.4); //4.75 US pints
converter(milesToKilometersRatio, "km", 34); //55.08 km

Я очень хотел бы, чтобы Дуглас Крокфорд в «JavaScript: Хорошие части» немного упомянул историю и фактическое использование карри, а не свои посторонние замечания. Долгое время после прочтения я был ошеломлен, пока не изучал функциональное программирование и не понял, откуда он пришел.

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

Байрон Кац
источник
2
Ваш код гораздо проще для понимания, чем в Prisoner Zero, и он решает ту же проблему без карри или чего-либо сложного. У тебя 2 больших пальца, а у него почти 100. Иди разберись.
DR01D
2

Это не магия или что-то еще ... просто приятное сокращение для анонимных функций.

partial(alert, "FOO!") эквивалентно function(){alert("FOO!");}

partial(Math.max, 0) соответствует function(x){return Math.max(0, x);}

Призывы к частичному ( терминология MochiKit . Я думаю, что некоторые другие библиотеки предоставляют функциям метод .curry, который делает то же самое) выглядят немного лучше и менее шумными, чем анонимные функции.

Marijn
источник
1

Что касается библиотек, использующих его, всегда есть Functional .

Когда это полезно в JS? Вероятно, в то же самое время это полезно в других современных языках, но я могу видеть, что использую его только в сочетании с частичным применением.

Хэнк Гей
источник
Спасибо, Хэнк. Не могли бы вы рассказать о том, когда это будет полезно в целом?
Дэйв Нолан
1

Я бы сказал, что, скорее всего, все библиотеки анимации в JS используют каррирование. Вместо того, чтобы передавать для каждого вызова набор затронутых элементов и функцию, описывающую, как должен вести себя элемент, в функцию более высокого порядка, которая обеспечит все функции синхронизации, как правило, клиенту легче выпустить в качестве публичного API-интерфейса. такие функции, как «slideUp», «fadeIn», которые принимают только элементы в качестве аргументов, и это всего лишь некоторая карри-функция, возвращающая функцию высокого порядка со встроенной «функцией анимации» по умолчанию.

штуковина
источник
Почему лучше использовать функцию более высокого уровня, чем просто вызывать ее по умолчанию?
Дэйв Нолан
1
Потому что гораздо более модульно иметь возможность каррировать «doMathOperation» с добавлением / умножением / квадратом / модулем / другим расчетом по желанию, чем представлять все «значения по умолчанию», которые может поддерживать более высокая функция.
штуковина
1

Вот пример.

Я обрабатываю кучу полей с помощью JQuery, чтобы видеть, что делают пользователи. Код выглядит так:

$('#foo').focus(trackActivity);
$('#foo').blur(trackActivity);
$('#bar').focus(trackActivity);
$('#bar').blur(trackActivity);

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

Теперь оказывается, что одно из этих полей должно обрабатываться по-разному. Я хотел бы иметь возможность передать параметр в одном из этих вызовов для передачи в нашу инфраструктуру отслеживания. С карри я могу.

Уильям Пьетри
источник
1

Функции JavaScript называется lamda на другом функциональном языке. Его можно использовать для создания нового API (более мощной или полной функции) на основе простого ввода другого разработчика. Карри - только одна из техник. Вы можете использовать его для создания упрощенного API для вызова сложного API. Если вы разработчик, который использует упрощенный API (например, вы используете JQuery для простых манипуляций), вам не нужно использовать карри. Но если вы хотите создать упрощенный API, карри ваш друг. Вы должны написать фреймворк javascript (например, jQuery, mootools) или библиотеку, тогда вы сможете оценить его мощь. Я написал расширенную функцию карри, в http://blog.semanticsworks.com/2011/03/enhanced-curry-method.html, Вам не нужен метод curry для выполнения карри, он просто помогает выполнять карри, но вы всегда можете сделать это вручную, написав функцию A () {} для возврата другой функции B () {}. Чтобы сделать его более интересным, используйте функцию B (), чтобы вернуть другую функцию C ().

Fred Yang
источник
1

Я знаю его старый поток, но мне придется показать, как это используется в библиотеках javascript:

Я буду использовать библиотеку lodash.js для конкретного описания этих понятий.

Пример:

var fn = function(a,b,c){ 
return a+b+c+(this.greet || '); 
}

Частичное применение:

var partialFnA = _.partial(fn, 1,3);

Карринг:

var curriedFn = _.curry(fn);

Переплет:

var boundFn = _.bind(fn,object,1,3 );//object= {greet: ’!'}

использование:

curriedFn(1)(3)(5); // gives 9 
or 
curriedFn(1,3)(5); // gives 9 
or 
curriedFn(1)(_,3)(2); //gives 9


partialFnA(5); //gives 9

boundFn(5); //gives 9!

разница:

после карри мы получаем новую функцию без параметров.

после частичного применения мы получаем функцию, которая связана с некоторыми предварительно связанными параметрами.

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

Посоветуйте: нет необходимости изобретать велосипед. Частичное применение / связывание / каррирование очень тесно связано. Вы можете увидеть разницу выше. Используйте это значение везде, и люди поймут, что вы делаете, без проблем с пониманием, плюс вам придется использовать меньше кода.

Шишир Арора
источник
0

Я согласен с тем, что иногда вы хотели бы добиться успеха, создав псевдофункцию, в которой всегда будет указано значение первого аргумента. К счастью, я натолкнулся на совершенно новую библиотеку JavaScript с именем jPaq (h ttp: // jpaq.org/ ), который обеспечивает эту функциональность. Самое лучшее в библиотеке - это то, что вы можете загрузить свою собственную сборку, которая содержит только тот код, который вам понадобится.

Кларенс Фредерикс
источник
0

Просто хотел добавить несколько ресурсов для Functional.js:

Лекция / конференция с описанием некоторых приложений http://www.youtube.com/watch?v=HAcN3JyQoyY

Обновлена ​​библиотека Functional.js: https://github.com/loop-recur/FunctionalJS. Несколько хороших помощников (извините, новинок здесь нет, репутации нет: p): / loop-recur / PreludeJS

В последнее время я много использую эту библиотеку, чтобы уменьшить количество повторений в библиотеке помощников клиентов IRC js. Это отличный материал - действительно помогает очистить и упростить код.

Кроме того, если производительность становится проблемой (но эта библиотека довольно легкая), ее легко переписать, используя встроенную функцию.

megawac
источник
0

Вы можете использовать нативную привязку для быстрого решения в одну строку

function clampAngle(min, max, angle) {
    var result, delta;
    delta = max - min;
    result = (angle - min) % delta;
    if (result < 0) {
        result += delta;
    }
    return min + result;
};

var clamp0To360 = clampAngle.bind(null, 0, 360);

console.log(clamp0To360(405)) // 45

cstuncsik
источник
0

Еще один удар от работы с обещаниями.

(Отказ от ответственности: JS Noob, пришедший из мира Python. Даже там, карри не так уж и часто используется, но иногда может пригодиться. Поэтому я отключил функцию карри - см. Ссылки)

Во-первых, я начинаю с вызова AJAX. У меня есть какая-то конкретная обработка, которую нужно выполнить в случае успеха, но в случае неудачи я просто хочу дать пользователю обратную связь, что вызов чего-либо привел к некоторой ошибке . В моем собственном коде я отображаю сообщение об ошибке на панели начальной загрузки, но здесь я просто использую логирование.

Я изменил мой живой URL, чтобы это не получилось.

function ajax_batch(e){
    var url = $(e.target).data("url");

    //induce error
    url = "x" + url;

    var promise_details = $.ajax(
        url,
        {
            headers: { Accept : "application/json" },
            // accepts : "application/json",
            beforeSend: function (request) {
                if (!this.crossDomain) {
                    request.setRequestHeader("X-CSRFToken", csrf_token);
                }
        },
        dataType : "json",
        type : "POST"}
    );
    promise_details.then(notify_batch_success, fail_status_specific_to_batch);
}

Теперь, чтобы сообщить пользователю о сбое пакета, мне нужно записать эту информацию в обработчик ошибок, потому что все, что он получает, - это ответ от сервера.

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

function fail_status_specific_to_batch(d){
    console.log("bad batch run, dude");
    console.log("response.status:" + d.status);
}

Давай сделаем это. Консольный вывод:

приставка:

bad batch run, dude utility.js (line 109) response.status:404

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

    ... rest is as before...
    var target = $(e.target).text();
    var context = {"user_msg": "bad batch run, dude.  you were calling :" + target};
    var contexted_fail_notification = curry(generic_fail, context); 

    promise_details.then(notify_batch_success, contexted_fail_notification);
}

function generic_fail(context, d){
    console.log(context);
    console.log("response.status:" + d.status);
}

function curry(fn) {
     var slice = Array.prototype.slice,
        stored_args = slice.call(arguments, 1);
     return function () {
        var new_args = slice.call(arguments),
              args = stored_args.concat(new_args);
        return fn.apply(null, args);
     };
}

приставка:

Object { user_msg="bad batch run, dude. you were calling :Run ACL now"} utility.js (line 117) response.status:404 utility.js (line 118)

В более общем плане, учитывая широкое использование обратных вызовов в JS, каррирование кажется довольно полезным инструментом.

https://javascriptweblog.wordpress.com/2010/04/05/curry-cooking-up-tastier-functions/ http://www.drdobbs.com/open-source/curring-and-partial-functions-in- javasc / 231001821? PGNO = 2

JL Peyret
источник