Карі JavaScript: які практичні програми?


172

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

Де ви використовуєте currying в JavaScript (або де основні бібліотеки використовують його)? Маніпуляції з DOM або загальні приклади розробки додатків вітаються.

В одній з відповідей згадується анімація. Такі функції, як slideUp, fadeInприймають елемент як аргумент і, як правило, є криволінійною функцією, що повертає функцію високого порядку із вбудованою за замовчуванням «функцією анімації». Чому це краще, ніж просто застосувати функцію вищого рівня з деякими типовими настройками?

Чи є якісь недоліки в його використанні?

Як вимагається тут, є кілька хороших ресурсів для JavaScript currying:

Я додам більше, коли вони з’являться у коментарях.


Так, згідно з відповідями, каррі та часткове застосування загалом - це зручні прийоми.

Якщо ви часто «доопрацьовуєте» функцію високого рівня, викликаючи її з однаковою конфігурацією, ви можете довірити (або використовувати часткову Резігу) функцію вищого рівня для створення простих, стислих допоміжних методів.


чи можете ви додати посилання на ресурс, який описує, що таке JS currying? підручник або допис у блозі буде чудовим.
Ерік Шкоуновер

2
svendtofte.com - це довгомотка, але якщо пропустити весь розділ із "Курсу аварійних ситуацій в ML" і знову почати з "Як писати цікавий JavaScript", це стане чудовим вводом до currying у js.
danio

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
До речі, версія частки Резіга є дефіцитною (звичайно, не "на гроші"), тому що вона, ймовірно, вийде з ладу, якщо одному з попередньо ініціалізованих ("кривих") аргументів буде надано значення не визначене . Усі, хто цікавиться гарною функцією каррінгу, повинні отримати оригінал від funcitonal.js Олівера Стіла , оскільки у нього немає такої проблеми.
RobG

Відповіді:


35

@Hank Gay

У відповідь на коментар EmbiggensTheMind:

Я не можу придумати приклад, коли curry - сам по собі - корисний у JavaScript; це методика перетворення функціональних викликів з декількома аргументами в ланцюги викликів функцій з одним аргументом для кожного виклику, але JavaScript підтримує декілька аргументів в одному виклику функції.

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

Ви можете використовувати часткове додаток / currying, щоб виправити ті відомі значення та повернути функцію, яка приймає лише невідомі, до якої буде викликано пізніше, коли у вас є фактично значення, які ви хочете передати. Це забезпечує чудовий спосіб уникнути повторення, коли ви б знову і знову викликали ті самі вбудовані 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" );


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)));
    }
}

5
Це чудово! Я бачу це схоже на цитату lisp, яка говорить "Lisp - це програмована мова програмування"
santiagobasulto

2
Цікаво, але цей приклад, здається, не працює. offset+inputбуде undefined + 1.60936у вашому milesToKmприкладі; що призводить до NaN.
Натан Лонг

3
@Nathan - компенсацію не можна визначити - вона за замовчуванням до 0
AngusC

6
З того, що я читав (тільки зараз), "каррі" зазвичай не є частиною мішків хитрощів Функції, якщо ви не використовуєте бібліотеку прототипів або не додаєте її самостійно. Дуже круто, хоча.
Робопрог

11
Те ж саме можна отримати методом ES5 bind (). Bind створює нову функцію, яка при виклику викликає оригінальну функцію з контекстом її першого аргументу та з подальшою послідовністю аргументів (перед будь-якими переданими до нової функції). Отже, ви можете зробити ... var milesToKm = converter.bind (це, 'км', 1.60936); або var farenheitToCelsius = converter.bind (це, "градуси С", 0,5556, -32); Перший аргумент, це контекст, тут не має значення, щоб ви могли просто пройти невизначене. Звичайно, вам потрібно буде розширити прототип базової функції за допомогою власного методу прив’язки для нерезервного 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. Це корисно в ситуаціях подій або для економії місця, оскільки воно, як правило, скорочує код.

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


7

Погоджуючись з Хенком Гей - Це надзвичайно корисно в певних справжніх функціональних мовах програмування - адже це необхідна частина. Наприклад, у Haskell ви просто не можете прийняти кілька параметрів до функції - цього не можна робити в чистому функціональному програмуванні. Ви берете один парам за один раз і нарощуєте свою функцію. У JavaScript це просто непотрібно, незважаючи на надумані приклади типу "конвертер". Ось той самий код перетворювача без необхідності в curry:

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
Ваш код набагато простіше зрозуміти, ніж ув'язнений Нуль, і він вирішує ту саму проблему, не маючи кривди чи нічого складного. У вас 2 великих пальця вгору, а у нього майже 100. Перейдіть фігуру.
DR01D

2

Це не магія чи що-небудь ... просто приємна скорочення для анонімних функцій.

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

partial(Math.max, 0) відповідає function(x){return Math.max(0, x);}

Заклики до часткової ( термінологія MochiKit . Я думаю, що деякі інші бібліотеки дають функції методу .curry, який робить те саме) виглядають трохи приємніше і менш галасливими, ніж анонімні функції.


1

Що стосується бібліотек, які ним користуються, то тут завжди функціональні .

Коли це корисно в JS? Можливо, в ті самі часи це корисно і в інших сучасних мовах, але єдиний раз, коли я бачу себе, використовуючи його, це поєднує часткове застосування.


Дякую Хенку - будь ласка, можете розширити, коли це корисно взагалі?
Дейв Нолан

1

Я б сказав, що, швидше за все, всі бібліотеки анімації в JS використовують каррі. Замість того, щоб передавати для кожного виклику набір елементів, що впливають, та функцію, описуючи, як елемент повинен вести себе, до функції вищого порядку, яка забезпечить всі дані про хронометраж, його клієнту взагалі простіше звільнити, як публічний API така функція, як "slideUp", "fadeIn", яка приймає лише елементи як аргументи, і це лише якась крива функція, яка повертає функцію високого порядку із вбудованою за замовчуванням "функцією анімації".


Чому краще завищити функцію Higherup, а не просто називати її за допомогою дефолтів?
Дейв Нолан

1
Оскільки надзвичайно модульним є можливість виправити "doMathOperation" з додаванням / множенням / квадратом / модулем / іншим вирахуванням за бажанням, ніж уявляти всі "за замовчуванням", які вища функція може підтримувати.
gizmo

1

Ось приклад.

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

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

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

Тепер виявляється, що з одним із цих полів потрібно обробляти інакше. Я хотів би мати можливість передавати параметр в одному з тих викликів, які будуть передані нашій інфраструктурі відстеження. З currying, я можу.


1

Іншими функціональними мовами функції JavaScript називають lamda. Він може бути використаний для створення нового api (більш потужної або складної функції) на основі простого введення іншого розробника. Карі - лише одна з технік. Ви можете використовувати його для створення спрощеного api для виклику складного api. Якщо ви - розробник, який використовує спрощену версію api (наприклад, ви використовуєте jQuery для виконання простих маніпуляцій), вам не потрібно використовувати каррі. Але якщо ви хочете створити спрощену api, каррі - ваш друг. Вам потрібно написати рамку javascript (наприклад, jQuery, mootools) або бібліотеку, щоб ви могли оцінити її потужність. Я написав розширену функцію каррі на http://blog.semanticsworks.com/2011/03/enhanced-curry-method.html. Вам не потрібно методом каррі робити каррінг, він просто допомагає робити каррінг, але ви завжди можете це зробити вручну, написавши функцію A () {}, щоб повернути ще одну функцію B () {}. Щоб зробити це цікавіше, використовуйте функцію B (), щоб повернути іншу функцію C ().


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!

різниця:

після завивки ми отримуємо нову функцію без попередньо зв'язаних параметрів.

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

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

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


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

Нещодавно я дуже часто використовував цю бібліотеку, щоб зменшити повторення в бібліотеці помічників js IRC-клієнтів. Це чудові речі - справді допомагає очистити та спростити код.

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


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


0

Ще одна удара в цьому - від роботи з обіцянками.

(Відмова: JS noob, що походить зі світу Python. Навіть там каррі використовується не так багато, але він може стати в нагоді при нагоді. Тому я заробив функцію каррінгу - див. Посилання)

По-перше, я починаю з виклику аякса. У мене є певна обробка для успіху, але в разі відмови, я просто хочу надати користувачеві відгук про те, що виклик чогось призвів до певної помилки . У своєму фактичному коді я відображаю відгуки про помилки на панелі завантаження, але тут я просто використовую журнал.

Я змінив свою живу 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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.