Quindi vuoi essere un programmatore funzionale (parte 1)

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.

Imparare a guidare

Quando abbiamo imparato a guidare per la prima volta, abbiamo lottato. Sembrava facile quando abbiamo visto altre persone farlo. Ma si è rivelato più difficile di quanto pensassimo.

Ci siamo allenati con la macchina dei nostri genitori e non ci siamo davvero avventurati in autostrada fino a quando non avremo dominato le strade nel nostro quartiere.

Ma attraverso la pratica ripetuta e alcuni momenti di panico che i nostri genitori vorrebbero dimenticare, abbiamo imparato a guidare e finalmente abbiamo ottenuto la nostra patente.

Con la nostra patente in mano, prenderemmo la macchina ogni possibilità che potremmo. Con ogni viaggio siamo sempre meglio e la nostra fiducia è aumentata. Poi è arrivato il giorno in cui abbiamo dovuto guidare l'auto di qualcun altro o la nostra auto ha finalmente rinunciato al fantasma e abbiamo dovuto comprarne una nuova.

Com'è stata la prima volta al volante di una macchina diversa? È stata la prima volta al volante? Neanche vicino. La prima volta, è stato tutto così straniero. Siamo stati in una macchina prima di allora, ma solo come passeggero. Questa volta eravamo al posto di guida. Quello con tutti i controlli.

Ma quando abbiamo guidato la nostra seconda auto, ci siamo semplicemente posti alcune semplici domande come: dove va la chiave, dove sono le luci, come si usano gli indicatori di direzione e come si regolano gli specchietti laterali.

Dopodiché, è stata una navigazione abbastanza fluida. Ma perché questa volta è stato così facile rispetto alla prima volta?

Questo perché la nuova auto era praticamente simile alla vecchia. Aveva tutte le stesse cose di base di cui un'auto ha bisogno ed erano praticamente nello stesso posto.

Alcune cose sono state implementate in modo diverso e forse aveva alcune funzionalità aggiuntive, ma non le abbiamo utilizzate la prima volta che abbiamo guidato o anche la seconda. Alla fine, abbiamo appreso tutte le nuove funzionalità. Almeno quelli a cui tenevamo.

Bene, imparare i linguaggi di programmazione è un po 'così. Il primo è il più difficile. Ma una volta che ne hai uno sotto la cintura, quelli successivi sono più facili.

Quando si avvia una seconda lingua per la prima volta, si pongono domande del tipo: "Come si crea un modulo? Come si cerca un array? Quali sono i parametri della funzione di sottostringa? "

Sei fiducioso di poter imparare a guidare questa nuova lingua perché ti ricorda la tua vecchia lingua con forse alcune cose nuove per rendere la vita più facile.

La tua prima astronave

Che tu abbia guidato una macchina per tutta la vita o dozzine di macchine, immagina di stare al volante di un'astronave.

Se avessi intenzione di volare su un'astronave, non ti aspetteresti che la tua abilità di guida su strada ti aiuti molto. Ricominceresti da zero quadrato. (Dopotutto siamo programmatori. Contiamo a partire da zero.)

Inizierai il tuo allenamento con l'aspettativa che le cose siano molto diverse nello spazio e che volare con questo aggeggio sia molto diverso dalla guida a terra.

La fisica non è cambiata. Proprio il modo in cui navighi all'interno di quello stesso universo.

Ed è lo stesso con l'apprendimento della programmazione funzionale. Dovresti aspettarti che le cose saranno molto diverse. E molto di ciò che sai sulla programmazione non si tradurrà.

La programmazione è pensare e la Programmazione funzionale ti insegnerà a pensare in modo molto diverso. Tanto che probabilmente non tornerai mai più al vecchio modo di pensare.

Dimentica tutto quello che sai

Alla gente piace dire questa frase, ma è un po 'vero. Imparare la programmazione funzionale è come iniziare da zero. Non completamente, ma efficacemente. Esistono molti concetti simili, ma è meglio se ti aspetti di dover imparare di nuovo tutto.

Con la giusta prospettiva avrai le giuste aspettative e con le giuste aspettative non ti lascerai quando le cose si fanno difficili.

Ci sono tutti i tipi di cose che sei abituato a fare come programmatore che non puoi più fare con la programmazione funzionale.

Proprio come nella tua auto, facevi il backup per uscire dal vialetto. Ma in un'astronave non c'è inversione. Ora potresti pensare: “COSA? NESSUN INVERSO ?! Come diavolo ho dovuto guidare senza retromarcia ?! ”

Bene, si scopre che non è necessario il contrario in un'astronave a causa della sua capacità di manovra nello spazio tridimensionale. Una volta capito questo, non ti perderai mai più il contrario. In effetti, un giorno, ripenserai a quanto fosse davvero limitante la macchina.

L'apprendimento della programmazione funzionale richiede del tempo. Quindi sii paziente.

Quindi lasciamo il freddo mondo della Programmazione imperativa e facciamo un tuffo nelle sorgenti termali della Programmazione funzionale.

Ciò che segue in questo articolo in più parti sono concetti di programmazione funzionale che ti aiuteranno prima di immergerti nel tuo primo linguaggio funzionale. O se hai già fatto il grande passo, questo ti aiuterà a completare la tua comprensione.

Per favore, non affrettarti. Prenditi il ​​tuo tempo a leggere da questo punto in avanti e prenditi il ​​tempo per capire gli esempi di codifica. Potresti anche voler smettere di leggere dopo ogni sezione per far affondare le idee. Quindi tornare più tardi per finire.

La cosa più importante è che tu capisca.

Purezza

Quando i programmatori funzionali parlano di purezza, si riferiscono a funzioni pure.

Le funzioni pure sono funzioni molto semplici. Operano solo sui parametri di input.

Ecco un esempio in Javascript di una funzione pura:

var z = 10;
funzione add (x, y) {
    ritorna x + y;
}

Si noti che la funzione di aggiunta NON tocca la variabile z. Non legge da z e non scrive su z. Legge solo xey, i suoi input e restituisce il risultato di sommarli.

Questa è una funzione pura. Se la funzione di aggiunta accedesse a z, non sarebbe più pura.

Ecco un'altra funzione da considerare:

funzione justTen () {
    ritorno 10;
}

Se la funzione, justTen, è pura, allora può solo restituire una costante. Perché?

Perché non gli abbiamo dato alcun input. E poiché, per essere puri, non può accedere a nient'altro che ai propri input, l'unica cosa che può restituire è una costante.

Poiché le funzioni pure che non accettano parametri non funzionano, non sono molto utili. Sarebbe meglio se justTen fosse definito come costante.

Le funzioni Pure più utili devono accettare almeno un parametro.

Considera questa funzione:

funzione addNoReturn (x, y) {
    var z = x + y
}

Nota come questa funzione non restituisce nulla. Aggiunge xey e lo inserisce in una variabile z ma non lo restituisce.

È una funzione pura poiché si occupa solo dei suoi input. Aggiunge, ma dal momento che non restituisce i risultati, è inutile.

Tutte le utili funzioni pure devono restituire qualcosa.

Consideriamo di nuovo la prima funzione di aggiunta:

funzione add (x, y) {
    ritorna x + y;
}
console.log (add (1, 2)); // stampa 3
console.log (add (1, 2)); // stampa ancora 3
console.log (add (1, 2)); // Stampa SEMPRE 3

Si noti che add (1, 2) è sempre 3. Non una grande sorpresa, ma solo perché la funzione è pura. Se la funzione di aggiunta utilizzava un valore esterno, non è possibile prevederne il comportamento.

Le funzioni pure producono sempre lo stesso output dati gli stessi input.

Poiché le funzioni pure non possono modificare alcuna variabile esterna, tutte le seguenti funzioni sono impure:

WriteFile (fileName);
updateDatabaseTable (Sqlcmd);
sendAjaxRequest (ajaxRequest);
openSocket (ipAddress);

Tutte queste funzioni hanno quelli che sono chiamati effetti collaterali. Quando li chiami, cambiano file e tabelle di database, inviano dati a un server o chiamano il sistema operativo per ottenere un socket. Fanno molto più che semplicemente operare sui loro ingressi e uscite di ritorno. Pertanto, non è mai possibile prevedere cosa restituiranno queste funzioni.

Le funzioni pure non hanno effetti collaterali.

Nei linguaggi di programmazione imperativa come Javascript, Java e C #, gli effetti collaterali sono ovunque. Ciò rende molto difficile il debug perché una variabile può essere modificata in qualsiasi punto del programma. Quindi, quando hai un bug perché una variabile viene cambiata con il valore sbagliato al momento sbagliato, dove guardi? Ovunque? Questo non è buono.

A questo punto, probabilmente stai pensando: "IN CHE MODO FACCIO QUALCOSA CON SOLO FUNZIONI PURE ?!"

In Programmazione funzionale, non scrivi semplicemente Funzioni pure.

I linguaggi funzionali non possono eliminare gli effetti collaterali, possono solo limitarli. Poiché i programmi devono interfacciarsi con il mondo reale, alcune parti di ogni programma devono essere impure. L'obiettivo è ridurre al minimo la quantità di codice impuro e separarlo dal resto del nostro programma.

Immutabilità

Ti ricordi quando hai visto per la prima volta il seguente bit di codice:

var x = 1;
x = x + 1;

E chi ti ha insegnato ti ha detto di dimenticare ciò che hai imparato durante le lezioni di matematica? In matematica, x non può mai essere uguale a x + 1.

Ma in Programmazione imperativa, significa, prendere il valore corrente di x aggiungere 1 e riportare quel risultato in x.

Bene, nella programmazione funzionale, x = x + 1 è illegale. Quindi devi ricordare cosa hai dimenticato in matematica ... In un certo senso.

Non ci sono variabili nella programmazione funzionale.

I valori memorizzati sono ancora chiamati variabili a causa della storia ma sono costanti, vale a dire una volta che x assume un valore, è quel valore per la vita.

Non preoccuparti, x è di solito una variabile locale, quindi la sua vita è generalmente breve. Ma mentre è vivo, non può mai cambiare.

Ecco un esempio di variabili costanti in Elm, un puro linguaggio di programmazione funzionale per lo sviluppo Web:

addOneToSum y z =
    permettere
        x = 1
    nel
        x + y + z

Se non hai familiarità con la sintassi ML-Style, lasciami spiegare. addOneToSum è una funzione che accetta 2 parametri, y e z.

All'interno del blocco let, x è legato al valore di 1, ovvero è uguale a 1 per il resto della sua vita. La sua durata termina quando la funzione viene chiusa o più accuratamente quando viene valutato il blocco let.

All'interno del blocco in, il calcolo può includere valori definiti nel blocco let, vale a dire. X. Il risultato del calcolo x + y + z viene restituito o, più precisamente, viene restituito 1 + y + z poiché x = 1.

Ancora una volta, posso sentirti chiedere: "QUANTO DIAMO BISOGNO DI FARE QUALCOSA SENZA VARIABILI ?!"

Pensiamo a quando vogliamo modificare le variabili. Ci sono 2 casi generali che vengono in mente: modifiche a più valori (ad es. Modifica di un singolo valore di un oggetto o record) e modifiche a valore singolo (ad esempio contatori di loop).

La programmazione funzionale gestisce le modifiche ai valori in un record effettuando una copia del record con i valori modificati. Lo fa in modo efficiente senza dover copiare tutte le parti del record utilizzando strutture di dati che lo rendono possibile.

La programmazione funzionale risolve la modifica a valore singolo esattamente nello stesso modo, facendone una copia.

Oh, sì e non avendo anelli.

“COSA SENZA VARIABILI E ORA SENZA LOOP ?! TI ODIO!!!"

Resisti. Non è che non possiamo fare loop (nessun gioco di parole previsto), è solo che non ci sono costrutti loop specifici come, mentre, fai, ripeti, ecc.

La programmazione funzionale utilizza la ricorsione per eseguire il looping.

Ecco due modi in cui puoi fare loop in Javascript:

// costrutto loop semplice
var acc = 0;
per (var i = 1; i <= 10; ++ i)
    acc + = i;
console.log (acc); // stampa 55
// senza costrutto loop o variabili (ricorsione)
funzione sumRange (inizio, fine, acc) {
    if (inizio> fine)
        ritorno acc;
    return sumRange (inizio + 1, fine, acc + inizio)
}
console.log (sumRange (1, 10, 0)); // stampa 55

Si noti come la ricorsione, l'approccio funzionale, compia lo stesso del ciclo for chiamandosi con un nuovo inizio (inizio + 1) e un nuovo accumulatore (acc + inizio). Non modifica i vecchi valori. Invece utilizza nuovi valori calcolati dal vecchio.

Sfortunatamente, questo è difficile da vedere in Javascript anche se passi un po 'di tempo a studiarlo, per due motivi. Uno, la sintassi di Javascript è rumorosa e due, probabilmente non sei abituato a pensare in modo ricorsivo.

In Elm, è più facile da leggere e, quindi, capire:

sumRange inizio fine acc =
    se inizio> fine allora
        acc
    altro
        sumRange (inizio + 1) fine (acc + inizio)

Ecco come funziona:

sumRange 1 10 0 = - sumRange (1 + 1) 10 (0 + 1)
sumRange 2 10 1 = - sumRange (2 + 1) 10 (1 + 2)
sumRange 3 10 3 = - sumRange (3 + 1) 10 (3 + 3)
sumRange 4 10 6 = - sumRange (4 + 1) 10 (6 + 4)
sumRange 5 10 10 = - sumRange (5 + 1) 10 (10 + 5)
sumRange 6 10 15 = - sumRange (6 + 1) 10 (15 + 6)
sumRange 7 10 21 = - sumRange (7 + 1) 10 (21 + 7)
sumRange 8 10 28 = - sumRange (8 + 1) 10 (28 + 8)
sumRange 9 10 36 = - sumRange (9 + 1) 10 (36 + 9)
sumRange 10 10 45 = - sumRange (10 + 1) 10 (45 + 10)
sumRange 11 10 55 = - 11> 10 => 55
55

Probabilmente stai pensando che per i loop sia più facile da capire. Mentre questo è discutibile e più probabilmente un problema di familiarità, i loop non ricorsivi richiedono la mutabilità, il che è negativo.

Non ho spiegato interamente i vantaggi dell'immutabilità qui, ma dai un'occhiata alla sezione Stato mutevole globale in Perché i programmatori hanno bisogno di limiti per saperne di più.

Un ovvio vantaggio è che se hai accesso a un valore nel tuo programma, hai solo accesso in lettura, il che significa che nessun altro può modificare quel valore. Anche tu. Quindi nessuna mutazione accidentale.

Inoltre, se il tuo programma è multi-thread, allora nessun altro thread può estrarre il tappeto da sotto di te. Quel valore è costante e se un altro thread vuole cambiarlo, dovrà creare un nuovo valore da quello vecchio.

Verso la metà degli anni '90, ho scritto un Game Engine per Creature Crunch e la più grande fonte di bug erano i problemi di multithreading. Vorrei conoscere l'immutabilità di allora. Ma allora ero più preoccupato per la differenza tra un drive CD-ROM a velocità 2x o 4x sulle prestazioni del gioco.

L'immutabilità crea un codice più semplice e sicuro.

Il mio cervello!!!!

Per ora basta.

Nelle parti successive di questo articolo, parlerò di funzioni di ordine superiore, composizione funzionale, curricula e altro ancora.

Avanti: Parte 2

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