JavaScript карри: каковы практические приложения?
Я не думаю, что я грэк карри еще. Я понимаю, что он делает, и как это сделать. Я просто не могу придумать ситуацию, которую я бы использовал.
Где вы используете каррирование в JavaScript (или где его используют основные библиотеки)? Приветствуются манипуляции с DOM или общие примеры разработки приложений.
В одном из ответов упоминается анимация. Функции как slideUp
, fadeIn
взять элемент в качестве аргумента и обычно является функцией карри, возвращающей функцию высокого порядка со встроенной "функцией анимации" по умолчанию. Почему это лучше, чем просто применение функции более высокого уровня с некоторыми значениями по умолчанию?
Есть ли недостатки в его использовании?
Как и просили, вот несколько хороших ресурсов по JavaScript карринг:
- http://www.dustindiaz.com/javascript-curry/
- Крокфорд, Дуглас (2008) JavaScript: хорошие части
- http://www.crockford.com/javascript/www_svendtofte_com/code/curried_javascript/index.html(Делает обходной путь в ML, поэтому пропустите весь раздел из "Ускоренного курса в ML" и начните снова с "Как писать JavaScript с карри")
- http://web.archive.org/web/20111217011630/http://blog.morrisjohns.com:80/javascript_closures_for_dummies
- Как работают JavaScript-закрытия?
- http://ejohn.org/blog/partial-functions-in-javascript/ (мистер Ресиг о деньгах как обычно)
- http://benalman.com/news/2010/09/partial-application-in-javascript/
Я добавлю больше, как они появляются в комментариях.
Итак, согласно ответам, карринг и частичное применение в целом являются удобными приемами.
Если вы часто "дорабатываете" высокоуровневую функцию, вызывая ее с одинаковой конфигурацией, вы можете каррировать (или использовать частичку Resig) высокоуровневую функцию для создания простых, лаконичных вспомогательных методов.
18 ответов
@ Хэнк Гей
В ответ на комментарий 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" );
Вот интересное И практическое использование каррирования в 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))); } }
Согласиться с Хэнком Гей - Это чрезвычайно полезно в некоторых настоящих функциональных языках программирования - потому что это необходимая часть. Например, в 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. Похоже, что редкий случай использования.
Я нашел функции, которые похожи на 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
, Это полезно в ситуациях события или для экономии места, потому что это обычно делает код короче.
Карринг похож на частичный с той разницей, что функция, которую возвращает карри, принимает только один аргумент (насколько я понимаю).
Рассмотрим функцию. И вы хотите написать для него обратный вызов.
let x = [1,2,3,4,5,6,7,11,12,14,15];
let results = x.filter(callback);
Предположим, вы хотите вывести только четные числа, поэтому:
let callback = x => x % 2 === 0;
Теперь представьте, что мы хотим реализовать наш так, чтобы в зависимости от сценария он выводил четные числа, которые превышают некоторое пороговое число (такое число должно быть настраиваемым).
Мы не можем легко сделать такое пороговое число параметром для работы, потому что
filter
призывает
callback
и по умолчанию передает ему элементы массива и индекс.
Как бы вы это реализовали?
Это хороший пример использования каррирования:
Вот пример.
Я обрабатываю кучу полей с помощью JQuery, чтобы видеть, что делают пользователи. Код выглядит так:
$('#foo').focus(trackActivity);
$('#foo').blur(trackActivity);
$('#bar').focus(trackActivity);
$('#bar').blur(trackActivity);
(Для пользователей, не являющихся пользователями JQuery, я говорю, что всякий раз, когда пара полей получает или теряет фокус, я хочу вызывать функцию trackActivity(). Я также мог бы использовать анонимную функцию, но мне пришлось бы дублировать ее 4 раза, поэтому я вытащил его и назвал.)
Теперь оказывается, что одно из этих полей должно обрабатываться по-другому. Я хотел бы иметь возможность передать параметр в одном из этих вызовов для передачи в нашу инфраструктуру отслеживания. С карри я могу.
Это не магия или что-то еще... просто приятное сокращение для анонимных функций.
partial(alert, "FOO!")
эквивалентно function(){alert("FOO!");}
partial(Math.max, 0)
соответствует function(x){return Math.max(0, x);}
Призывы к частичному (терминология MochiKit. Я думаю, что некоторые другие библиотеки предоставляют функциям метод.curry, который делает то же самое) выглядят немного лучше и менее шумными, чем анонимные функции.
Я знаю его старый поток, но мне придется показать, как это используется в библиотеках 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', если не ограничено, по умолчанию любой функцией будет область видимости окна.
Посоветуйте: нет необходимости изобретать велосипед. Частичное применение / связывание / каррирование очень тесно связано. Вы можете увидеть разницу выше. Используйте это значение где угодно, и люди поймут, что вы делаете, без проблем в понимании, плюс вам придется использовать меньше кода.
Функции 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().
Что касается библиотек, использующих его, всегда есть Functional.
Когда это полезно в JS? Вероятно, в то же самое время это полезно в других современных языках, но я могу видеть, что использую его только в сочетании с частичным применением.
Я бы сказал, что, скорее всего, все библиотеки анимации в JS используют каррирование. Вместо того, чтобы передавать для каждого вызова набор затронутых элементов и функцию, описывающую, как должен вести себя элемент, в функцию более высокого порядка, которая обеспечит все функции синхронизации, что, как правило, проще для клиента для выпуска, в качестве публичного API-интерфейса. такие функции, как "slideUp", "fadeIn", которые принимают только элементы в качестве аргументов, и это всего лишь некоторая карри-функция, возвращающая функцию высокого порядка со встроенной по умолчанию "функцией анимации".
Просто хотел добавить несколько ресурсов для Functional.js:
Лекция / конференция с описанием некоторых приложений http://www.youtube.com/watch?v=HAcN3JyQoyY
Обновлена библиотека Functional.js: https://github.com/loop-recur/FunctionalJS Несколько хороших помощников (извините, новинок здесь нет, репутации нет:p): /loop-recur/PreludeJS
В последнее время я много использую эту библиотеку, чтобы уменьшить количество повторений в библиотеке помощников клиентов IRC js. Это отличная штука - действительно помогает очистить и упростить код.
Кроме того, если производительность становится проблемой (но эта библиотека довольно легкая), ее легко переписать, используя встроенную функцию.
Здесь у вас есть практический пример использования каррирования в данный момент. https://www.joshwcomeau.com/react/demystifying-styled-components/
По сути, он создает компоненты в стиле бедняка и использует каррирование для «предварительной загрузки» имени тега при создании для него нового стиля.
Я согласен, что иногда вы хотели бы добиться успеха, создав псевдофункцию, в которой всегда будет указано значение первого аргумента. К счастью, я натолкнулся на совершенно новую библиотеку JavaScript с именем jPaq (h http://jpaq.org/), который обеспечивает эту функциональность. Самое лучшее в библиотеке - это то, что вы можете загрузить свою собственную сборку, которая содержит только тот код, который вам понадобится.
Я задал аналогичный вопрос на https://softwareengineering.stackexchange.com/questions/384529/a-real-life-example-of-using-curry-function
Но только после того, как я использую рамду , я наконец оцениваю полезность карри. Итак, я буду утверждать, что если нам нужно объединить функции вместе для обработки некоторых входных данных по одному шагу за раз, например, пример цепочки обещаний в статье « Выбор карри» с использованием карри по принципу «сначала функция, затем данные», код действительно выглядит чистым!
Вы можете использовать нативную привязку для быстрого решения в одну строку
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
Я только что написал пример jPaq, который показывает несколько классных приложений функции карри. Проверьте это здесь: Curring Up Строковые функции
Еще один удар от работы с обещаниями.
(Отказ от ответственности: 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/currying-and-partial-functions-in-javasc/231001821?pgno=2