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 obalfunction(a)
. - Když se nazývá jako
curriedSum(1)
, argument se uloží v Lexikálním prostředí a vrátí se nový obalfunction(b)
. - Pak se tato obálka zavolá s
2
jako argument a předá volání původnímusum
.
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:
- Po kari jsme nic neztratili:
log
je stále normálně volatelný. - 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:
- 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
. - 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žijecurried
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élkouCurrying 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.
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)
).