javascript - Vad är 'Currying'?
Jag har sett referenser till curryade funktioner i flera artiklar och bloggar men jag kan inte hitta en bra förklaring (eller åtminstone en som är begriplig!)
Svar #1
Currying är när du bryter ner en funktion som tar flera argument till en serie av funktioner som var och en tar endast ett argument. Här är ett exempel i JavaScript:
function add (a, b) {
return a + b;
}
add(3, 4); // returns 7
Detta är en funktion som tar två argument, a och b, och returnerar deras summa. Vi kommer nu att curry denna funktion:
function add (a) {
return function (b) {
return a + b;
}
}
Det här är en funktion som tar ett argument, a, och returnerar en funktion som tar ett annat argument, b, och den funktionen returnerar deras summa.
add(3)(4); // returns 7
var add3 = add(3); // returns a function
add3(4); // returns 7
- Det första uttalandet returnerar
7, precis somadd(3, 4)-uttalandet. - Det andra uttalandet definierar en ny funktion kallad
add3som kommer att lägga till3till sitt argument. Detta är vad vissa kan kalla en closure. - Det tredje uttalandet använder
add3-operationen för att lägga till3till4, vilket återigen ger 7 som resultat.
Svar #2
I en algebra av funktioner är det något opraktiskt att hantera funktioner som tar flera argument (eller motsvarande ett argument som är ett N-tal) -- men, som Moses Schönfinkel (och, oberoende, Haskell Curry) bevisade, är det inte nödvändigt: allt du behöver är funktioner som tar ett argument.
Så hur hanterar man något som man naturligt skulle uttrycka som, säg, f(x,y)? Jo, man tar det som motsvarande f(x)(y) -- f(x), låt oss kalla det g, är en funktion, och du tillämpar den funktionen på y. Med andra ord har du bara funktioner som tar ett argument -- men några av dessa funktioner returnerar andra funktioner (som OCKSÅ tar ett argument;-).
Som vanligt har Wikipedia en bra sammanfattande artikel om detta, med många användbara tips (troligtvis inklusive sådana som rör dina favoritprogrammeringsspråk;-) samt något mer rigorös matematisk behandling.
Svar #3
Här är ett konkret exempel:
Antag att du har en funktion som beräknar den gravitationella kraften som verkar på ett objekt. Om du inte känner till formeln kan du hitta den här. Denna funktion tar in de tre nödvändiga parametrarna som argument.
Nu, när du är på jorden, vill du bara beräkna krafter för objekt på denna planet. I ett funktionellt språk kan du skicka in jordens massa till funktionen och sedan delvis utvärdera den. Vad du får tillbaka är en annan funktion som bara tar två argument och beräknar den gravitationella kraften för objekt på jorden. Detta kallas currying.
Svar #4
Det kan vara ett sätt att använda funktioner för att skapa andra funktioner.
I JavaScript:
let add = function(x){
return function(y){
return x + y
};
};
Skulle tillåta oss att kalla det så:
let addTen = add(10);
När detta körs passerar 10 in som x;
let add = function(10){
return function(y){
return 10 + y
};
};
vilket betyder att vi får tillbaka denna funktion:
function(y) { return 10 + y };
Så när du ringer
addTen();
du ringer verkligen:
function(y) { return 10 + y };
Så om du gör detta:
addTen(4)
det är samma som:
function(4) { return 10 + 4} // 14
Så vår addTen() lägger alltid till tio till vad vi än skickar in. Vi kan skapa liknande funktioner på samma sätt:
let addTwo = add(2) // addTwo(); will add two to whatever you pass in
let addSeventy = add(70) // ... and so on...
Nu är den uppenbara följdfrågan varför i hela världen skulle du någonsin vilja göra det? Det förvandlar en operation som tidigare var ivrig x + y till en som kan genomföras långsamt, vilket betyder att vi kan göra åtminstone två saker: 1. cache:a dyra operationer 2. uppnå abstraktioner inom det funktionella paradigmet.
Föreställ dig att vår currade funktion såg ut så här:
let doTheHardStuff = function(x) {
let z = doSomethingComputationallyExpensive(x)
return function (y){
z + y
}
}
Vi skulle kunna kalla på denna funktion en gång, och sedan skicka vidare resultatet för att användas på många ställen, vilket innebär att vi bara gör de beräkningstunga sakerna en gång:
let finishTheJob = doTheHardStuff(10)
finishTheJob(20)
finishTheJob(30)
Vi kan få abstraktioner på ett liknande sätt.
Svar #5
Currying är en transformation som kan tillämpas på funktioner för att göra det möjligt för dem att ta ett argument mindre än tidigare.
Till exempel, i F# kan du definiera en funktion så här:-
let f x y z = x + y + z
Här tar funktionen f parametrarna x, y och z och summerar dem tillsammans så:-
f 1 2 3
Returnerar 6.
Från vår definition kan vi därför definiera curry-funktionen för f:-
let curry f = fun x -> f x
Där 'fun x -> f x' är en lambda-funktion som motsvarar x => f(x) i C#. Denna funktion tar in funktionen du vill curryfiera och returnerar en funktion som tar ett enda argument och returnerar den specificerade funktionen med det första argumentet satt till det inmatade argumentet.
Med hjälp av vårt tidigare exempel kan vi erhålla en curry av f på följande sätt:-
let curryf = curry f
Vi kan sedan göra följande:-
let f1 = curryf 1
Vilket ger oss en funktion f1 som är ekvivalent med f1 y z=1 + y + z. Detta betyder att vi kan göra följande:-
f1 2 3
Vilket returnerar 6.
Denna process förväxlas ofta med 'delvis funktionsanvändning', vilket kan definieras på följande sätt:-
let papply f x = f x
Även om vi kan utvidga det till mer än en parameter, dvs:-
let papply2 f x y = f x y
let papply3 f x y z = f x y z
etc.
En partiell tillämpning tar funktionen och parameter(erna) och returnerar en funktion som kräver en eller flera färre parametrar, och som de två tidigare exemplen visar implementeras detta direkt i standard F#-funktionsdefinitionen så vi kunde uppnå det tidigare resultatet på följande sätt:-
let f1 = f 1
f1 2 3
Vilket kommer att ge ett resultat på 6.
Sammanfattningsvis:-
Skillnaden mellan currying och partiell funktionsapplikation är att:-
Currying tar en funktion och ger en ny funktion som accepterar ett enda argument och returnerar den angivna funktionen med dess första argument satt till det argumentet. Detta gör att vi kan representera funktioner med flera parametrar som en serie av funktioner med ett argument. Exempel:-
let f x y z = x + y + z
let curryf = curry f
let f1 = curryf 1
let f2 = curryf 2
f1 2 3
6
f2 1 3
6
Delfunktionstillämpning är mer direkt - den tar en funktion och ett eller flera argument och returnerar en funktion med de första n argumenten satta till de n angivna argumenten. Exempel:-
let f x y z = x + y + z
let f1 = f 1
let f2 = f 2
f1 2 3
6
f2 1 3
6
Svar #6
En currerad funktion är en funktion med flera argument omskriven så att den tar det första argumentet och returnerar en funktion som tar det andra argumentet och så vidare. Detta gör det möjligt för funktioner med flera argument att delvis tillämpa några av sina initiala argument.
Svar #7
Currying innebär att omvandla en funktion med N argument till N funktioner med 1 argument vardera. Arity för funktionen är antalet argument den kräver.
Här är den formella definitionen:
curry(f) :: (a,b,c) -> f(a) -> f(b)-> f(c)
Här är ett verkligt exempel som är logiskt:
Du går till uttagsautomaten för att ta ut lite pengar. Du drar ditt kort, anger PIN-kod och gör ditt val och trycker sedan på enter för att skicka "beloppet" tillsammans med förfrågan.
här är den normala funktionen för att ta ut pengar.
const withdraw=(cardInfo,pinNumber,request){
// process it
return request.amount
}
I denna implementation förväntar sig funktionen att vi anger alla argument på en gång. Vi skulle dra kortet, ange PIN-koden och göra förfrågan, sedan skulle funktionen köras. Om något av dessa steg hade problem, skulle du få reda på det efter att du angett alla argument. Med curried-funktion skulle vi skapa funktioner med högre aritet, rena och enkla. Rena funktioner kommer att hjälpa oss att enkelt felsöka vår kod.
detta är Atm med curryad funktion:
const withdraw=(cardInfo)=>(pinNumber)=>(request)=>request.amount
uttagsautomat tar kortet som inmatning och returnerar en funktion som förväntar sig pinNummer och denna funktion returnerar en funktion som accepterar begäran-objektet och efter att processen har lyckats får du det belopp du begärde. Vid varje steg, om du fick ett fel, kan du lätt förutsäga vad som gick fel. Låt oss säga att du sätter in kortet och fick ett fel, du vet att det antingen är relaterat till kortet eller maskinen men inte till pin-koden. Eller om du angav pin-koden och den inte accepteras, vet du att du angav fel pin-kod. Du kommer enkelt kunna felsöka felet.
Dessutom är varje funktion här återanvändbar, så du kan använda samma funktioner i olika delar av ditt projekt.
Svar #8
Currying är att översätta en funktion från att kunna anropas som f(a, b, c) till att kunna anropas som f(a)(b)(c).
Annars är currying när man bryter ner en funktion som tar flera argument till en serie funktioner som tar en del av argumenten.
Bokstavligen är currying en transformation av funktioner: från ett sätt att anropa till ett annat. I JavaScript gör vi vanligtvis en omslagare för att behålla den ursprungliga funktionen.
Currying anropar inte en funktion. Det transformeras bara.
Låt oss skapa en curryfunktion som utför currying för tvåargumentfunktioner. Med andra ord, curry(f) för tvåargument f(a, b) översätter den till 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 carriedSum = curry(sum);
alert( carriedSum(1)(2) ); // 3
Som du kan se är implementationen en serie av omslag.
- Resultatet av
curry(func)är en omslags-funktion(a). - När det kallas som
sum(1), sparas argumentet i det lexikala miljön, och en ny wrapper returnerasfunction(b). - Då kallar
sum(1)(2)slutligenfunction(b)och tillhandahåller 2, och det skickar vidare anropet till den ursprungliga summan med flera argument.
