Cos’è il ‘Currying’?

Ho visto riferimenti a funzioni curry in diversi articoli e blog ma non riesco a trovare una buona spiegazione (o almeno una che abbia senso!)

Il currying è quando si analizza una funzione che prende più argomenti in una serie di funzioni che prendono parte agli argomenti. Ecco un esempio in JavaScript:

 function add (a, b) { return a + b; } add(3, 4); // returns 7 

Questa è una funzione che accetta due argomenti, a e b, e restituisce la loro sum. Eseguiremo ora questa funzione:

 function add (a) { return function (b) { return a + b; } } 

Questa è una funzione che accetta un argomento, a, e restituisce una funzione che prende un altro argomento, b, e quella funzione restituisce la loro sum.

 add(3)(4); var add3 = add(3); add3(4); 

La prima dichiarazione restituisce 7, come l’istruzione add (3, 4). La seconda istruzione definisce una nuova funzione chiamata add3 che aggiungerà 3 al suo argomento. Questo è ciò che alcune persone potrebbero chiamare una chiusura. La terza istruzione usa l’operazione add3 per aggiungere da 3 a 4, producendo nuovamente 7 come risultato.

In un’algebra di funzioni, occuparsi di funzioni che richiedono più argomenti (o un argomento equivalente che è una N-tupla) è in qualche modo inelegante – ma, come ha dimostrato Moses Schönfinkel (e, in modo indipendente, Haskell Curry), non è necessario: tutto ciò bisogno sono le funzioni che accettano un argomento.

Quindi, come gestisci qualcosa che esprimeresti naturalmente come, ad esempio, f(x,y) ? Bene, lo prendi come equivalente a f(x)(y)f(x) , chiamalo g , è una funzione, e applichi quella funzione a y . In altre parole, hai solo funzioni che accettano un argomento, ma alcune di queste funzioni restituiscono altre funzioni (che ANCHE accettano un argomento ;-).

Come al solito, wikipedia ha una bella voce riassuntiva su questo, con molti suggerimenti utili (probabilmente compresi quelli riguardanti le tue lingue preferite 😉 e un trattamento matematico leggermente più rigoroso.

Ecco un esempio concreto:

Supponiamo di avere una funzione che calcola la forza gravitazionale che agisce su un object. Se non conosci la formula, puoi trovarla qui . Questa funzione accetta i tre parametri necessari come argomenti.

Ora, essendo sulla terra, vuoi solo calcolare le forze per gli oggetti su questo pianeta. In un linguaggio funzionale, potresti passare la massa della terra alla funzione e quindi valutarla parzialmente. Quello che torneresti è un’altra funzione che prende solo due argomenti e calcola la forza gravitazionale degli oggetti sulla terra. Questo è chiamato currying.

Il Currying è una trasformazione che può essere applicata alle funzioni per consentire loro di dedicare un argomento in meno rispetto a prima.

Ad esempio, in F # è ansible definire una funzione in questo modo: –

 let fxyz = x + y + z 

Qui la funzione f prende i parametri x, yez e li sum insieme in modo da:

 f 1 2 3 

Restituisce 6.

Dalla nostra definizione possiamo quindi definire la funzione curry per f: –

 let curry f = fun x -> fx 

Dove ‘fun x -> fx’ è una funzione lambda che equivale a x => f (x) in C #. Questa funzione immette la funzione che si desidera curry e restituisce una funzione che accetta un singolo argomento e restituisce la funzione specificata con il primo argomento impostato sull’argomento di input.

Usando il nostro esempio precedente possiamo ottenere un curry di f così: –

 let curryf = curry f 

Possiamo quindi fare quanto segue: –

 let f1 = curryf 1 

Il che ci fornisce una funzione f1 che è equivale a f1 yz = 1 + y + z. Questo significa che possiamo fare quanto segue: –

 f1 2 3 

Che restituisce 6.

Questo processo è spesso confuso con la “funzione di applicazione parziale” che può essere definita così:

 let papply fx = fx 

Sebbene possiamo estenderlo a più di un parametro, ovvero:

 let papply2 fxy = fxy let papply3 fxyz = fxyz etc. 

Un’applicazione parziale prenderà la funzione e i parametri e restituirà una funzione che richiede uno o più parametri in meno e, come mostrano i due esempi precedenti, viene implementata direttamente nella definizione della funzione F # standard in modo da ottenere il risultato precedente in questo modo:

 let f1 = f 1 f1 2 3 

Quale restituirà un risultato di 6.

In conclusione:-

La differenza tra l’applicazione di curry e di funzione parziale è che:

Currying accetta una funzione e fornisce una nuova funzione accettando un singolo argomento e restituendo la funzione specificata con il primo argomento impostato su quell’argomento. Questo ci permette di rappresentare le funzioni con più parametri come una serie di funzioni a singolo argomento . Esempio:-

 let fxyz = x + y + z let curryf = curry f let f1 = curryf 1 let f2 = curryf 2 f1 2 3 6 f2 1 3 6 

L’applicazione di funzione parziale è più diretta: prende una funzione e uno o più argomenti e restituisce una funzione con i primi n argomenti impostati sugli argomenti n specificati. Esempio:-

 let fxyz = x + y + z let f1 = f 1 let f2 = f 2 f1 2 3 6 f2 1 3 6 

Una funzione al curry è una funzione di diversi argomenti riscritti in modo tale che accetta il primo argomento e restituisce una funzione che accetta il secondo argomento e così via. Ciò consente alle funzioni di diversi argomenti di applicare parzialmente alcuni dei loro argomenti iniziali.

Può essere un modo per usare le funzioni per fare altre funzioni.

In javascript:

 let add = function(x){ return function(y){ return x + y }; }; 

Ci permetterebbe di chiamarlo così:

 let addTen = add(10); 

Quando viene eseguito, il 10 viene passato come x ;

 let add = function(10){ return function(y){ return 10 + y }; }; 

il che significa che ci viene restituita questa funzione:

 function(y) { 10 + y }; 

Quindi quando chiami

  addTen(); 

stai davvero chiamando:

  function(y) { 10 + y }; 

Quindi se lo fai:

  addTen(4) 

è lo stesso di:

 function(4) { 10 + 4} // 14 

Quindi il nostro addTen() aggiunge sempre dieci a tutto ciò che passiamo. Possiamo fare funzioni simili allo stesso modo:

 let addTwo = add(2) // addTwo(); will add two to whatever you pass in let addSeventy = add(70) // ... and so on... 

Ecco un esempio di giocattolo in Python:

 >>> from functools import partial as curry >>> # Original function taking three parameters: >>> def display_quote(who, subject, quote): print who, 'said regarding', subject + ':' print '"' + quote + '"' >>> display_quote("hoohoo", "functional languages", "I like Erlang, not sure yet about Haskell.") hoohoo said regarding functional languages: "I like Erlang, not sure yet about Haskell." >>> # Let's curry the function to get another that always quotes Alex... >>> am_quote = curry(display_quote, "Alex Martelli") >>> am_quote("currying", "As usual, wikipedia has a nice summary...") Alex Martelli said regarding currying: "As usual, wikipedia has a nice summary..." 

(Basta usare la concatenazione tramite + per evitare distrazioni per i programmatori non Python.)

Modifica da aggiungere:

Vedi http://docs.python.org/library/functools.html?highlight=partial#functools.partial , che mostra anche la distinzione tra object parziale e funzione nel modo in cui Python implementa questo.

Ho trovato questo articolo e l’articolo a cui fa riferimento, utile, per capire meglio il currying: http://blogs.msdn.com/wesdyer/archive/2007/01/29/currying-and-partial-function-application.aspx

Come menzionato dagli altri, è solo un modo per avere una funzione a parametro unico.

Ciò è utile in quanto non è necessario assumere il numero di parametri da passare, quindi non è necessario un parametro 2, 3 parametri e 4 funzioni parametro.

Se capisci che partial sei a metà strada. L’idea di partial è di applicare gli argomenti a una funzione e restituire una nuova funzione che vuole solo gli argomenti rimanenti. Quando viene chiamata questa nuova funzione, include gli argomenti precaricati insieme a qualunque argomento gli sia stato fornito.

In Clojure + è una funzione ma per rendere le cose chiaramente chiare:

 (defn add [ab] (+ ab)) 

Si può essere consapevoli che la funzione inc semplicemente aggiunge 1 a qualsiasi numero che è passato.

 (inc 7) # => 8 

Costruiamolo noi stessi usando il partial :

 (def inc (partial add 1)) 

Qui restituiamo un’altra funzione che ha 1 caricato nel primo argomento di add . Come add prende due argomenti la nuova funzione inc vuole solo l’argomento b – non 2 argomenti come prima poiché 1 è già stato parzialmente applicato. Quindi partial è uno strumento dal quale creare nuove funzioni con i valori predefiniti presupposti. Ecco perché in un linguaggio funzionale le funzioni spesso ordinano argomenti da generali a specifici. Ciò rende più facile riutilizzare tali funzioni dalle quali build altre funzioni.

Ora immagina se il linguaggio fosse abbastanza intelligente da comprendere in modo introspettivo che add due argomenti ricercati. Quando l’abbiamo passato a un argomento, piuttosto che a rimbalzare, cosa succederebbe se la funzione applicasse parzialmente l’argomentazione che abbiamo trasmesso per nostro conto comprendendo che probabilmente intendevamo fornire l’altro argomento in un secondo momento? Potremmo quindi definire inc senza usare esplicitamente il partial .

 (def inc (add 1)) #partial is implied 

Questo è il modo in cui si comportano alcune lingue. È eccezionalmente utile quando si desidera comporre funzioni in trasformazioni più grandi. Questo porterebbe uno ai trasduttori.

Una funzione al curry viene applicata a più elenchi di argomenti, invece di uno solo.

Ecco una funzione regolare, non-curried, che aggiunge due parametri Int, x e y:

 scala> def plainOldSum(x: Int, y: Int) = x + y plainOldSum: (x: Int,y: Int)Int scala> plainOldSum(1, 2) res4: Int = 3 

Ecco una funzione simile che è al curry. Invece di un elenco di due parametri Int, si applica questa funzione a due elenchi di un parametro Int ciascuno:

 scala> def curriedSum(x: Int)(y: Int) = x + y curriedSum: (x: Int)(y: Int)Intscala> second(2) res6: Int = 3 scala> curriedSum(1)(2) res5: Int = 3 

Quello che sta succedendo qui è che quando invochi curriedSum , in realtà ottieni due tradizionali invocazioni di funzioni back to back. La prima funzione invoca un singolo parametro Int chiamato x e restituisce un valore di funzione per la seconda funzione. Questa seconda funzione prende il parametro Int y .

Ecco una funzione chiamata in first che fa in spirito ciò che farebbe la prima funzione tradizionale invocazione di curriedSum :

 scala> def first(x: Int) = (y: Int) => x + y first: (x: Int)(Int) => Int 

Applicando 1 alla prima funzione, in altre parole, invocando la prima funzione e passando per 1, si ottiene la seconda funzione:

 scala> val second = first(1) second: (Int) => Int =  

L’applicazione di 2 alla seconda funzione produce il risultato:

 scala> second(2) res6: Int = 3 

Un esempio di curriculum sarebbe quando si hanno funzioni che si conosce solo uno dei parametri al momento:

Per esempio:

 func aFunction(str: String) { let callback = callback(str) // signature now is `NSData -> ()` performAsyncRequest(callback) } func callback(str: String, data: NSData) { // Callback code } func performAsyncRequest(callback: NSData -> ()) { // Async code that will call callback with NSData as parameter } 

Qui, dal momento che non si conosce il secondo parametro per il callback quando lo si invia a performAsyncRequest(_:) è necessario creare un altro lambda / closure per inviarlo alla funzione.

Per dare un esempio di curriculum del mondo reale (e potenzialmente utile) guarda come puoi effettuare chiamate al server in javascript con la libreria di recupero

  Get(url) { let fullUrl = toFullUrl(url); let promise = getPromiseForFetchWithToken((token) => { let headers = Object.assign( getDefaultHeaders(token), jsonHeaders); let config = { method: "GET", headers: headers }; return fetch(fullUrl, config); }); return promise; } 

Dove getPromiseForFetchWithToken è una funzione al curry che restituisce una Promise con il risultato del recupero, mostrato di seguito:

 function getPromiseForFetchWithToken(tokenConsumingFetch) { function resolver(resolve, reject) { let token = localStorage.getItem("token"); tokenConsumingFetch(token) .then(checkForError) .then((response) => { if (response) resolve(response); }) .catch(reject); } var promise = new Promise(resolver); return promise; } 

Ciò consente di attendere la chiamata della funzione Get e quindi gestire appropriatamente il valore di ritorno indipendentemente da cosa sia, è ansible riutilizzare la funzione getPromiseForFetchWithToken ovunque sia necessario effettuare una chiamata al server che deve includere un token al portatore. (Inserisci, Elimina, Pubblica, ecc.)

Come tutte le altre risposte in corso, aiuta a creare funzioni parzialmente applicate. Javascript non fornisce supporto nativo per la conversione automatica. Quindi gli esempi forniti sopra potrebbero non aiutare nella pratica della codifica. C’è un eccellente esempio in livescript (che essenzialmente compila in js) http://livescript.net/

 times = (x, y) --> x * y times 2, 3 #=> 6 (normal use works as expected) double = times 2 double 5 #=> 10 

Nell’esempio sopra quando hai dato meno no degli argomenti, il livescript genera una nuova funzione al curry per te (doppio)

Curry può semplificare il tuo codice. Questo è uno dei motivi principali per usare questo. Currying è un processo di conversione di una funzione che accetta n argomenti in n funzioni che accettano solo un argomento.

Il principio è passare gli argomenti della funzione passata, usando la proprietà closure (closure), per memorizzarli in un’altra funzione e trattarli come un valore di ritorno, e queste funzioni formano una catena, e gli argomenti finali vengono passati per completare l’operazione.

Il vantaggio di questo è che può semplificare l’elaborazione dei parametri trattando un parametro alla volta, il che può anche migliorare la flessibilità e la leggibilità del programma. Questo rende anche il programma più gestibile. Anche la suddivisione del codice in pezzi più piccoli lo renderebbe riutilizzabile.

Per esempio:

 function curryMinus(x) { return function(y) { return x - y; } } var minus5 = curryMinus(1); minus5(3); minus5(5); 

Posso anche fare …

 var minus7 = curryMinus(7); minus7(3); minus7(5); 

Questo è molto utile per rendere il codice complesso ordinato e la gestione di metodi non sincronizzati, ecc.