2016-07-17 137 views
7

Attualmente sto leggendo il Mostly Adequate Guide on functional programming, chapter 2.Dov'è finito l'argomento in questo esempio?

Lì, il seguente esempio è dato

var getServerStuff = function(callback) { 
    return ajaxCall(function(json) { 
    return callback(json); 
    }); 
}; 

che viene poi riscritta in:

var getServerStuff = ajaxCall; 

Mentre spiega il refactoring, l'autore sostiene che

return ajaxCall(function(json) { 
    return callback(json); 
}); 

è lo stesso come

return ajaxCall(callback); 

Pur comprendendo che ajaxCall viene chiamato con il valore di ritorno della funzione anonima (che è solo il valore di ritorno di callback), Non capisco come si suppone che la versione riscritta al lavoro - in cui ha fatto l'argomento json partire? Cosa mi manca?

+3

'ajaxCall' prende un singolo argomento chiamabile che a sua volta prende un singolo argomento, il' json'. Se tutto ciò che il callback fa è chiamare un'altra funzione con gli stessi argomenti, * può essere sostituita da quella funzione *. – jonrsharpe

+0

È simile, sebbene non esattamente equivalente, a ciò che viene chiamato * inlining * in lingue più imperative. –

+0

@jonrsharpe perché è così? perché può essere sostituito? C'è un nome per questo ? –

risposta

5

La questione è stata risolta, ma penso che un po 'di grassetto e barratura rendono molto facile per vedere le conversioni di codice. Speriamo che questa risposta aiuti chiunque stia cercando di visualizzare il problema.


Non si scrive & hellip;

var floor = function(x) { return Math.floor(x) } 

Invece, si scriverebbe & hellip;

var floor = Math.floor 

& hellip; e funzionerà esattamente lo stesso . Questo è chiamato Eta Conversion e se lo ripeti due volte, vedrai come Brian ha ottenuto il suo risultato nella Guida perlopiù adeguata.

La regola di base della conversione Eta è questo:

function(x) { return f(x) } === f

& hellip; sono completamente intercambiabili


È possibile utilizzare la stessa tecnica nel codice originale

var getServerStuff = function(callback) { 
    return ajaxCall(function(json) { 
    return callback(json) 
    }) 
} 

Primo sguardo al & hellip;

return ajaxCall(function(json) { return callback(json) }) 

Eta conversione dice & hellip;

function(json) { return callback(json) } === callback

Quindi diamo un'occhiata all'intero codice con i risultati della prima conversione eta & hellip;

// first step 
var getServerStuff = function(callback) { 
    return ajaxCall(function(json) { 
    returncallback(json) 
    }) 
} 

// eta converts to ... 
var getServerStuff = function(callback) { 
    return ajaxCall(callback) 
} 

Questo scenario dovrebbe esserci familiare. Un'altra conversione eta ci porterà alla forma semplificata finale.Aggiungerò in grassetto ancora una volta in modo che possiamo vederlo meglio

Eta conversione dice & hellip;

function(callback) { return ajaxCall(callback) } === ajaxCall
// second step 
var getServerStuff = function(callback) { 
    returnajaxCall(callback) 
} 

// eta converts to ... 
var getServerStuff = ajaxCall 

per tutti gli effetti, sono intercambiabili. La Guida alla FP perlopiù adeguata mostra scarsa attenzione per l'associazione o l'uso dinamico di this

+2

Ehi, grazie per la tua risposta! Sebbene abbia avuto grandi problemi a capirlo, l'audace/barrato mi ha davvero aiutato molto, quindi ho accettato la tua risposta. Questa è la cosa più incredibile di sempre! – Sven

1

Il punto è che

function (json) { 
    return callback(json) 
} 

è pari a poco

callback 

Non esiste una funzione di espressione più, quindi non c'è bisogno di avere un parametro json. Invece di chiamare la funzione anonima (che a sua volta chiama lo callback con l'argomento e ne restituisce il risultato), puoi semplicemente chiamare lo callback direttamente allo stesso effetto.

+0

Sono equivalenti solo sotto alcune ipotesi. – Oriol

+0

@Oriol: questa è la regola generale, chiamata [riduzione di eta] (https://wiki.haskell.org/Eta_conversion). – georg

+0

@Oriol: per gli scopi dell'articolo, ovviamente. Sì, richiama l'altezza dello stack, il valore 'this' e ulteriori argomenti, ovviamente, differiscono. Dato che l'articolo riguarda FP, possiamo supporre che il 'callback' sia una funzione unaria. – Bergi

2

Pur comprendendo che ajaxCall viene chiamato con il valore di ritorno della funzione anonima (che è solo il valore di ritorno di callback)

questo è in realtà indietro.

ajaxCall viene chiamato con una funzione anonima come argomento. Presumibilmente, alla fine, chiama tale funzione e tale funzione chiama quindi callback.

Generalmente, l'assegnazione di tutti i nomi di funzioni semplifica la comprensione.

function callback(json) { 
    console.log('I was called with ' + json); 
} 

function intermediate(json) { 
    return callback(json); 
} 

ajaxCall(intermediate); 

Qui, ajaxCall viene passato intermediate. Quando chiamato, intermediate prende il suo argomento singolo e lo passa lungo a callback.

Questa catena è un po 'inutile e può essere semplificata a questo:

function callback(json) { 
    console.log('I was called with ' + json); 
} 

ajaxCall(callback); 

Mi auguro che aiuta!

4

È un refactoring funzionale ed è molto più chiaro farlo viceversa. Immagina di avere una funzione, ajaxCall che accetta una funzione di callback che verrà applicata con il risultato es.

ajaxCall(fun); 

Se si dovesse sostituire fun con function(json){ return fun(json); } dalle regole di sostituzione è lo stesso codice esatto. Così si potrebbe scrivere:

ajaxCall(function(json){ return fun(json);}); 

Proprio come abbiamo avvolto fun possiamo avvolgere ajaxCall, introduciamo callback come una sostituzione per fun e dare fun come argomento. Con le regole di sostituzione la sua esattamente lo stesso codice:

(function(callback){ 
    return ajaxCall(function(json){ callback(json);}); 
})(fun) 

Ora, questo non è lo stesso che si ha dal momento che in realtà lo chiamo nel mio esempio, ma si può vedere una somiglianza con il codice quando ci siamo lasciati definizione e chiamata:

var getServerStuff = function(callback) { 
    return ajaxCall(function(json){return callback(json);}); 
}; 
getServerStuff(fun); 

Se si potesse seguire la catena il testo lo fa nel modo opposto sostituendo la funzione wrapper in argomento per ajaxCall:

var getServerStuff = function(callback) { 
    // return ajaxCall(function(json){return callback(json);}); 
    return ajaxCall(callback); 
}; 
getServerStuff(fun); 

e poi vedendo la getServerStuff in realtà è anche solo un wrapper senza caratteristiche aggiunte:

// var getServerStuff = function(callback){ return ajaxCall(callback);} 
var getServerStuff = ajaxCall; 
getServerStuff(fun); 

Poi getServerStuff è solo un alias in modo che possiamo sostituirlo con il valore:

ajaxCall(fun); 

ci si va. Proprio dove hai iniziato. Affinché questo funzioni, tuttavia, le variabili introdotte non possono utilizzare le variabili ombra utilizzate nel codice interessato e le funzioni sostituite devono supportare la stessa aritmia dell'originale. Oltre a questo è praticamente semplice.

Jim Weirich mostra how to do the factorial function using only anonymous functions effettuando i refactoring secondo le regole standard di refactoring. È in Ruby ma non ho avuto problemi a seguirlo. A 21:15 mostra il refactoring funzionale che è l'argomento importante in argomento.

2

getServerStuff è una funzione di ordine superiore che prevede una lambda (funzione anonima) come argomento (che di per sé si aspetta un argomento).

ajaxCall è una funzione di ordine superiore che prevede anche una lambda (che di per sé si aspetta un argomento).

Dove sono i dati (JSON)? Eliminiamo i dettagli di distrazione:

const hof = cb => hof2(x => cb(x)); // higher order function that expects a lambda 
const hof2 = cb => cb(); // another HOF that expects a lambda 
const inc = x => x + 1; 
hof(inc); // NaN 

Questo è inutile. Di nuovo, dove sono i dati? Vengono prelevati in modo asincrono da un server. Quindi abbiamo bisogno di adattare le nostre funzioni un po ':

const inc = x => console.log(x + 1); 
 
const hof = cb => hof2(x => cb(x)); 
 
const hof2 = cb => setTimeout(cb, 0, 2); // assumed `2` is the result of the async operation 
 
    
 
hof(inc); // 3

Alla fine abbiamo un HOF che chiama un altro HOF che recupera i dati da un server e applica la propria lambda (callback) per i dati .

Possiamo ovviamente semplificarlo ulteriormente.Abbiamo solo bisogno di un HOF che si aspetta un lambda che si aspetta che i dati, che è fornito in modo asincrono da un server:

const inc = x => console.log(x + 1); 
 
const hof2 = cb => setTimeout(cb, 0, 2); // assumed `2` is the result of the async operation 
 
    
 
hof2(inc); // 3

L'ultima riga è equivalente a ajaxCall(callback);

Conclusione: L'esempio è difficile da seguire, poiché i dati (json) vengono passati come risultato di una chiamata di funzione asincrona e non dal codice di chiamata iniziale.

Supplemento: se tutte le funzioni del codice sono unarie, si aspettano esattamente un argomento, quindi non c'è più aritmetica - è astratta. Questo porta alla composizione delle funzioni e allo stile senza punti, due argomenti che sono descritti anche nella Guida perlopiù adeguata.

Problemi correlati