Quindi vuoi essere un programmatore funzionale (parte 2)

Fare il primo passo per comprendere i concetti di Programmazione funzionale è il passo più importante e talvolta il più difficile. Ma non deve essere. Non con la giusta prospettiva.

Parti precedenti: parte 1

Promemoria amichevole

Si prega di leggere il codice lentamente. Assicurati di averlo capito prima di andare avanti. Ogni sezione si basa sulla sezione precedente.

Se ti precipiti, potresti perdere alcune sfumature che saranno importanti in seguito.

refactoring

Pensiamo al refactoring per un minuto. Ecco del codice Javascript:

funzione validateSsn (ssn) {
    if (/^\d{3}-\d{2}-\d{4}$/.exec(ssn))
        console.log ('Valid SSN');
    altro
        console.log ('SSN non valido');
}
funzione validatePhone (telefono) {
    if (/^\(\d{3}\)\d{3}-\d{4}$/.exec(phone))
        console.log ('Numero di telefono valido');
    altro
        console.log ('Numero di telefono non valido');
}

Abbiamo scritto tutto questo codice prima e nel tempo, iniziamo a riconoscere che queste due funzioni sono praticamente le stesse e differiscono solo per alcune cose (mostrate in grassetto).

Invece di copiare validateSsn e incollare e modificare per creare validatePhone, dovremmo creare una singola funzione e parametrizzare le cose che abbiamo modificato dopo aver incollato.

In questo esempio, parametrizzeremo il valore, l'espressione regolare e il messaggio stampato (almeno l'ultima parte del messaggio stampata).

Il codice refactored:

funzione validateValue (valore, regex, tipo) {
    if (regex.exec (value))
        console.log ('Invalid' + tipo);
    altro
        console.log ('Valido' + tipo);
}

I parametri ssn e phone nel vecchio codice sono ora rappresentati dal valore.

Le espressioni regolari / ^ \ d {3} - \ d {2} - \ d {4} $ / e / ^ \ (\ d {3} \) \ d {3} - \ d {4} $ / sono rappresentato da regex.

Infine, l'ultima parte del messaggio "SSN" e "Numero di telefono" sono rappresentati dal tipo.

Avere una funzione è molto meglio che avere due funzioni. O peggio tre, quattro o dieci funzioni. Ciò mantiene il codice pulito e gestibile.

Ad esempio, se c'è un bug, devi solo correggerlo in un posto invece di cercare in tutta la tua base di codice per trovare dove questa funzione potrebbe essere stata incollata e modificata.

Ma cosa succede quando si verifica la seguente situazione:

funzione validateAddress (indirizzo) {
    if (parseAddress (indirizzo))
        console.log ('Indirizzo valido');
    altro
        console.log ('Indirizzo non valido');
}
funzione validateName (nome) {
    if (parseFullName (name))
        console.log ('Nome valido');
    altro
        console.log ('Nome non valido');
}

Qui parseAddress e parseFullName sono funzioni che accettano una stringa e restituiscono true se analizza.

Come lo refattiamo?

Bene, possiamo usare il valore per indirizzo e nome e digitare "Indirizzo" e "Nome" come abbiamo fatto prima, ma esiste una funzione in cui si trovava la nostra espressione regolare.

Se solo potessimo passare una funzione come parametro ...

Funzioni di ordine superiore

Molte lingue non supportano le funzioni di passaggio come parametri. Alcuni lo fanno ma non lo rendono facile.

Nella programmazione funzionale, una funzione è un cittadino di prima classe della lingua. In altre parole, una funzione è solo un altro valore.

Poiché le funzioni sono solo valori, possiamo passarle come parametri.

Anche se Javascript non è un linguaggio funzionale puro, è possibile eseguire alcune operazioni funzionali con esso. Quindi ecco le ultime due funzioni refactored in una singola funzione passando la funzione di analisi come parametro chiamato parseFunc:

function validateValueWithFunc (value, parseFunc, type) {
    if (parseFunc (value))
        console.log ('Invalid' + tipo);
    altro
        console.log ('Valido' + tipo);
}

La nostra nuova funzione è chiamata funzione di ordine superiore.

Le funzioni di ordine superiore accettano funzioni come parametri, funzioni di ritorno o entrambe.

Ora possiamo chiamare la nostra funzione di ordine superiore per le quattro funzioni precedenti (funziona in Javascript perché Regex.exec restituisce un valore di verità quando viene trovata una corrispondenza):

validateValueWithFunc ('123-45-6789', /^\d{3}-\d{2}-\d{4}$/.exec, 'SSN');
validateValueWithFunc ('(123) 456-7890', /^\(\d{3}\)\d{3}-\d{4}$/.exec, 'Telefono');
validateValueWithFunc ('123 Main St.', parseAddress, 'Address');
validateValueWithFunc ('Joe Mama', parseName, 'Name');

È molto meglio che avere quattro funzioni quasi identiche.

Ma nota le espressioni regolari. Sono un po 'prolissi. Puliamo il nostro codice facendone il factoring:

var parseSsn = /^\d{3}-\d{2}-\d{4}$/.exec;
var parsePhone = /^\(\d{3}\)
validateValueWithFunc ('123-45-6789', parseSsn, 'SSN');
validateValueWithFunc ('(123) 456-7890', parsePhone, 'Telefono');
validateValueWithFunc ('123 Main St.', parseAddress, 'Address');
validateValueWithFunc ('Joe Mama', parseName, 'Name');

Così va meglio. Ora, quando vogliamo analizzare un numero di telefono, non dobbiamo copiare e incollare l'espressione regolare.

Ma immagina di avere espressioni più regolari da analizzare, non solo parsSsn e parsePhone. Ogni volta che creiamo un parser di espressioni regolari, dobbiamo ricordare di aggiungere .exec alla fine. E fidati di me, questo è facile da dimenticare.

Possiamo evitare questo creando una funzione di alto ordine che restituisce la funzione exec:

funzione makeRegexParser (regex) {
    return regex.exec;
}
var parseSsn = makeRegexParser (/ ^ \ d {3} - \ d {2} - \ d {4} $ /);
var parsePhone = makeRegexParser (/ ^ \ (\ d {3} \) \ d {3} - \ d {4} $ /);
validateValueWithFunc ('123-45-6789', parseSsn, 'SSN');
validateValueWithFunc ('(123) 456-7890', parsePhone, 'Telefono');
validateValueWithFunc ('123 Main St.', parseAddress, 'Address');
validateValueWithFunc ('Joe Mama', parseName, 'Name');

Qui makeRegexParser accetta un'espressione regolare e restituisce la funzione exec, che accetta una stringa. validateValueWithFunc passerà la stringa, valore, alla funzione di analisi, ovvero exec.

parseSsn e parsePhone sono effettivamente gli stessi di prima, la funzione exec dell'espressione regolare.

Certo, si tratta di un miglioramento marginale, ma viene mostrato qui per dare un esempio di una funzione di alto ordine che restituisce una funzione.

Tuttavia, puoi immaginare i vantaggi di apportare questa modifica se makeRegexParser era molto più complesso.

Ecco un altro esempio di una funzione di ordine superiore che restituisce una funzione:

funzione makeAdder (constantValue) {
    adder funzione di ritorno (valore) {
        return constantValue + value;
    };
}

Qui abbiamo makeAdder che prende constantValue e restituisce adder, una funzione che aggiungerà quella costante a qualsiasi valore venga passato.

Ecco come può essere utilizzato:

var add10 = makeAdder (10);
console.log (Add10 (20)); // stampa 30
console.log (Add10 (30)); // stampa 40
console.log (Add10 (40)); // stampa 50

Creiamo una funzione, add10, passando la costante 10 a makeAdder che restituisce una funzione che aggiungerà 10 a tutto.

Si noti che l'addizionatore di funzioni ha accesso a constantValue anche dopo la restituzione di makeAddr. Questo perché constantValue era nel suo ambito di applicazione quando è stato creato l'adder.

Questo comportamento è molto importante perché senza di esso le funzioni che restituiscono funzioni non sarebbero molto utili. Quindi è importante capire come funzionano e come si chiama questo comportamento.

Questo comportamento si chiama chiusura.

chiusure

Ecco un esempio inventato di funzioni che utilizzano le chiusure:

funzione grandParent (g1, g2) {
    var g3 = 3;
    parent funzione di ritorno (p1, p2) {
        var p3 = 33;
        funzione di ritorno figlio (c1, c2) {
            var c3 = 333;
            ritorna g1 + g2 + g3 + p1 + p2 + p3 + c1 + c2 + c3;
        };
    };
}

In questo esempio, il bambino ha accesso alle sue variabili, alle variabili del genitore e alle variabili del grandParent.

Il genitore ha accesso alle sue variabili e alle variabili di grandParent.

GrandParent ha accesso solo alle sue variabili.

(Vedi la piramide sopra per chiarimenti.)

Ecco un esempio del suo utilizzo:

var parentFunc = grandParent (1, 2); // restituisce parent ()
var childFunc = parentFunc (11, 22); // restituisce child ()
console.log (childFunc (111, 222)); // stampa 738
// 1 + 2 + 3 + 11 + 22 + 33 + 111 + 222 + 333 == 738

Qui, parentFunc mantiene attivo l'ambito del genitore poiché grandParent restituisce parent.

Allo stesso modo, childFunc mantiene vivo l'ambito del bambino poiché parentFunc, che è solo genitore, restituisce figlio.

Quando viene creata una funzione, tutte le variabili nel suo ambito al momento della creazione sono accessibili ad essa per tutta la durata della funzione. Una funzione esiste finché esiste ancora un riferimento ad essa. Ad esempio, l'ambito del bambino esiste fintanto che childFunc lo fa ancora riferimento.

Una chiusura è l'ambito di una funzione che viene mantenuta attiva da un riferimento a quella funzione.

Si noti che in Javascript le chiusure sono problematiche poiché le variabili sono mutabili, ovvero possono cambiare valori dal momento in cui sono state chiuse al momento in cui viene chiamata la funzione restituita.

Per fortuna, le variabili nei linguaggi funzionali sono immutabili eliminando questa fonte comune di bug e confusione.

Il mio cervello!!!!

Per ora basta.

Nelle parti successive di questo articolo, parlerò di Composizione funzionale, Currying, funzioni funzionali comuni (ad esempio mappa, filtro, piega ecc.) E altro ancora.

Avanti: Parte 3

Se ti è piaciuto, fai clic su in basso in modo che altre persone lo vedano qui su Medium.

Se vuoi unirti a una comunità di sviluppatori web che si apprendono e si aiutano a vicenda nello sviluppo di app Web utilizzando la Programmazione funzionale in Elm, controlla il mio gruppo Facebook, Scopri la programmazione Elm https://www.facebook.com/groups/learnelm/

Il mio Twitter: @cscalfani