Na kari

Currying je pokročilá technika práce s funkcemi. Používá se nejen v JavaScriptu, ale také v jiných jazycích.

Currying je transformace funkcí, která převádí funkci z callable jako f(a, b, c) do callable jako f(a)(b)(c) .

Currying nevolá funkci. Prostě to transformuje.

Podívejme se nejprve na příklad, abychom lépe pochopili, o čem mluvíme, a poté na praktické aplikace.

Vytvoříme pomocnou funkci curry(f) který provádí currying pro dva argumenty f . Jinými slovy, curry(f) pro dva argumenty f(a, b) převede jej na funkci, která běží jako f(a)(b) :

function curry(f) { // curry(f) does the currying transform
 return function(a) {
 return function(b) {
 return f(a, b);
 };
 };
}

// usage
function sum(a, b) {
 return a + b;
}

let curriedSum = curry(sum);

alert( curriedSum(1)(2) ); // 3

Jak vidíte, implementace je přímočará:jsou to jen dva obaly.

  • Výsledek curry(func) je obal function(a) .
  • Když se nazývá jako curriedSum(1) , argument se uloží v Lexikálním prostředí a vrátí se nový obal function(b) .
  • Pak se tato obálka zavolá s 2 jako argument a předá volání původnímu sum .

Pokročilejší implementace currying, jako je _.curry z knihovny lodash, vracejí obal, který umožňuje volat funkci normálně i částečně:

function sum(a, b) {
 return a + b;
}

let curriedSum = _.curry(sum); // using _.curry from lodash library

alert( curriedSum(1, 2) ); // 3, still callable normally
alert( curriedSum(1)(2) ); // 3, called partially

Chystáte se? K čemu?

Abychom pochopili výhody, potřebujeme důstojný příklad ze skutečného života.

Máme například funkci logování log(date, importance, message) který formátuje a vydává informace. Ve skutečných projektech mají tyto funkce mnoho užitečných funkcí, jako je odesílání protokolů přes síť, zde použijeme pouze alert :

function log(date, importance, message) {
 alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}

Pojďme to kari!

log = _.curry(log);

Poté log funguje normálně:

log(new Date(), "DEBUG", "some debug"); // log(a, b, c)

…Ale funguje také v kari formě:

log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)

Nyní můžeme snadno vytvořit komfortní funkci pro aktuální protokoly:

// logNow will be the partial of log with fixed first argument
let logNow = log(new Date());

// use it
logNow("INFO", "message"); // [HH:mm] INFO message

Nyní logNow je log s pevným prvním argumentem, jinými slovy „částečně aplikovaná funkce“ nebo zkráceně „částečně“.

Můžeme jít dále a vytvořit pohodlnou funkci pro aktuální protokoly ladění:

let debugNow = logNow("DEBUG");

debugNow("message"); // [HH:mm] DEBUG message

Takže:

  1. Po kari jsme nic neztratili:log je stále normálně volatelný.
  2. Můžeme snadno generovat dílčí funkce, například pro dnešní protokoly.

Pokročilá implementace kari

V případě, že byste se chtěli dostat do podrobností, zde je „pokročilá“ implementace kari pro víceargumentové funkce, kterou bychom mohli použít výše.

Je to docela krátké:

function curry(func) {

 return function curried(...args) {
 if (args.length >= func.length) {
 return func.apply(this, args);
 } else {
 return function(...args2) {
 return curried.apply(this, args.concat(args2));
 }
 }
 };

}

Příklady použití:

function sum(a, b, c) {
 return a + b + c;
}

let curriedSum = curry(sum);

alert( curriedSum(1, 2, 3) ); // 6, still callable normally
alert( curriedSum(1)(2,3) ); // 6, currying of 1st arg
alert( curriedSum(1)(2)(3) ); // 6, full currying

Nový curry může vypadat složitě, ale ve skutečnosti je snadno pochopitelný.

Výsledek curry(func) volání je obal curried vypadá to takto:

// func is the function to transform
function curried(...args) {
 if (args.length >= func.length) { // (1)
 return func.apply(this, args);
 } else {
 return function(...args2) { // (2)
 return curried.apply(this, args.concat(args2));
 }
 }
};

Když jej spustíme, jsou zde dvě if exekuční větve:

  1. Pokud bude předán args počet je stejný nebo větší než má původní funkce ve své definici (func.length ), pak mu stačí předat volání pomocí func.apply .
  2. V opačném případě získejte částečnou:func nevoláme zatím. Místo toho je vrácen jiný obal, který znovu použije curried poskytnutí předchozích argumentů spolu s novými.

Pak, když to zavoláme, znovu, dostaneme buď nový částečný (pokud není dostatek argumentů), nebo nakonec výsledek.

Pouze funkce s pevnou délkou

Currying vyžaduje, aby funkce měla pevný počet argumentů.

Funkce, která používá ostatní parametry, jako je f(...args) , nelze tímto způsobem karifikovat.

Trochu víc než kari

Podle definice by currying měl převést sum(a, b, c) do sum(a)(b)(c) .

Ale většina implementací curryingu v JavaScriptu je pokročilá, jak je popsáno:udržují také funkci volatelnou ve variantě s více argumenty.

Shrnutí

Na kari je transformace, která vytváří f(a,b,c) volatelné jako f(a)(b)(c) . Implementace JavaScriptu obvykle udržují funkci normálně volatelnou a vracejí částečnou, pokud počet argumentů nestačí.

Curry nám umožňuje snadno získat částečky. Jak jsme viděli v příkladu protokolování, po použití tří argumentové univerzální funkce log(date, importance, message) nám při volání s jedním argumentem (jako log(date) ) nebo dva argumenty (například log(date, importance) ).