Quindi vuoi essere un programmatore funzionale (parte 4)

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, parte 2, parte 3

accattivarsi

Se ricordi dalla Parte 3, la ragione per cui abbiamo avuto problemi a comporre mult5 e add (in) è perché mult5 accetta 1 parametro e add prende 2.

Possiamo risolverlo facilmente limitando tutte le funzioni a prendere solo 1 parametro.

Fidati di me. Non è così male come sembra.

Scriviamo semplicemente una funzione add che utilizza 2 parametri ma accetta solo 1 parametro alla volta. Le funzioni al curry ci consentono di farlo.

Una funzione al curry è una funzione che accetta solo un singolo parametro alla volta.

Questo ci permetterà di aggiungere il suo primo parametro prima di comporlo con mult5. Quindi quando viene chiamato mult5AfterAdd10, add otterrà il suo secondo parametro.

In Javascript, possiamo ottenere ciò riscrivendo add:

var add = x => y => x + y

Questa versione di add è una funzione che accetta un parametro ora e poi un altro successivamente.

Nel dettaglio, la funzione add accetta un singolo parametro, x, e restituisce una funzione che accetta un singolo parametro, y, che alla fine restituirà il risultato dell'aggiunta di xey.

Ora possiamo usare questa versione di add per creare una versione funzionante di mult5AfterAdd10:

var compose = (f, g) => x => f (g (x));
var mult5AfterAdd10 = compose (mult5, add (10));

La funzione compose accetta 2 parametri, f e g. Quindi restituisce una funzione che accetta 1 parametro, x, che quando viene chiamato applica f dopo g a x.

Quindi cosa abbiamo fatto esattamente? Bene, abbiamo convertito la nostra semplice vecchia funzione di aggiunta in una versione al curry. Ciò ha reso l'aggiunta più flessibile poiché il primo parametro, 10, può essere passato in primo piano e il parametro finale verrà passato quando viene chiamato mult5AfterAdd10.

A questo punto, ti starai chiedendo come riscrivere la funzione di aggiunta in Elm. Si scopre che non è necessario. In Elm e in altri linguaggi funzionali, tutte le funzioni vengono automaticamente impostate.

Quindi la funzione di aggiunta ha lo stesso aspetto:

aggiungi x y =
    x + y

Ecco come mult5AfterAdd10 avrebbe dovuto essere riscritto nella Parte 3:

mult5AfterAdd10 =
    (mult5 << aggiungi 10)

Sintatticamente parlando, Elm batte lingue imperative come Javascript perché è stato ottimizzato per cose funzionali come curry e composizione.

Currying e Refactoring

Un'altra volta il curry brilla è durante il refactoring quando si crea una versione generalizzata di una funzione con molti parametri e quindi la si utilizza per creare versioni specializzate con meno parametri.

Ad esempio, quando abbiamo le seguenti funzioni che mettono parentesi e doppie parentesi attorno alle stringhe:

parentesi str =
    "{" ++ str ++ "}"
doubleBracket str =
    "{{" ++ str ++ "}}"

Ecco come lo useremmo:

bracketedJoe =
    parentesi "Joe"
doubleBracketedJoe =
    doubleBracket "Joe"

Possiamo generalizzare parentesi quadre e doubleBracket:

prefisso generalBracket str suffisso =
    prefisso ++ suffisso str ++

Ma ora ogni volta che utilizziamo generalBracket dobbiamo passare tra parentesi:

bracketedJoe =
    generalBracket "{" "Joe" "}"
doubleBracketedJoe =
    generalBracket "{{" "Joe" "}}"

Quello che vogliamo davvero è il meglio dei due mondi.

Se riordiniamo i parametri di generalBracket, possiamo creare parentesi quadre e doubleBracket sfruttando il fatto che le funzioni sono ridotte:

Suffisso prefisso generalBracket str =
    prefisso ++ suffisso str ++
parentesi =
    generalBracket "{" "}"
doubleBracket =
    generalBracket "{{" "}}"

Si noti che inserendo prima i parametri che probabilmente erano più statici, cioè prefisso e suffisso, e mettendo i parametri che probabilmente avrebbero dovuto cambiare per ultimo, ovvero str, possiamo facilmente creare versioni specializzate di generalBracket.

L'ordine dei parametri è importante per sfruttare appieno il curry.

Si noti inoltre che parentesi quadra e doubleBracket sono scritti in notazione senza punti, ovvero è implicito il parametro str. Sia parentesi che doubleBracket sono funzioni in attesa del loro parametro finale.

Ora possiamo usarlo come prima:

bracketedJoe =
    parentesi "Joe"
doubleBracketedJoe =
    doubleBracket "Joe"

Ma questa volta stiamo usando una funzione al curry generalizzata, generalBracket.

Funzioni funzionali comuni

Diamo un'occhiata a 3 funzioni comuni utilizzate nei linguaggi funzionali.

Ma prima, diamo un'occhiata al seguente codice Javascript:

per (var i = 0; i 

C'è un grosso problema con questo codice. Non è un bug. Il problema è che questo codice è un codice boilerplate, cioè un codice che viene scritto più volte.

Se codifichi in lingue imperative come Java, C #, Javascript, PHP, Python, ecc., Ti ritroverai a scrivere questo codice di caldaia più di ogni altro.

Questo è ciò che non va.

Quindi uccidiamolo. Mettiamolo in una funzione (o un paio di funzioni) e non scrivere mai più un for-loop. Bene, quasi mai; almeno fino a quando non passiamo a un linguaggio funzionale.

Cominciamo con la modifica di un array chiamato cose:

var things = [1, 2, 3, 4];
per (var i = 0; i 

UGH !! Mutabilità!

Proviamo di nuovo. Questa volta non mutiamo le cose:

var things = [1, 2, 3, 4];
var newThings = [];
per (var i = 0; i 

Okay, quindi non abbiamo mutato le cose, ma tecnicamente abbiamo mutato nuove cose. Per ora, trascureremo questo. Dopotutto siamo in Javascript. Una volta passati a un linguaggio funzionale, non saremo in grado di mutare.

Il punto qui è capire come funzionano queste funzioni e aiutarci a ridurre il rumore nel nostro codice.

Prendiamo questo codice e mettiamolo in una funzione. Chiameremo la nostra prima mappa di funzioni comuni poiché associa ogni valore nel vecchio array ai nuovi valori nel nuovo array:

var map = (f, array) => {
    var newArray = [];
    per (var i = 0; i 

Si noti che la funzione, f, viene passata in modo che la nostra funzione mappa possa fare tutto ciò che vogliamo per ogni elemento dell'array.

Ora possiamo chiamare riscrivere il nostro codice precedente per usare map:

var things = [1, 2, 3, 4];
var newThings = map (v => v * 10, cose);

Guarda ma Nessun loop continuo. E molto più facile da leggere e quindi ragionare.

Bene, tecnicamente, ci sono for-loop nella funzione map. Ma almeno non dobbiamo più scrivere quel codice di targa.

Ora scriviamo un'altra funzione comune per filtrare le cose da un array:

var filter = (pred, array) => {
    var newArray = [];
per (var i = 0; i 

Notate come la funzione predicato, pred, restituisce VERO se manteniamo l'oggetto o FALSO se lo lanciamo.

Ecco come utilizzare il filtro per filtrare i numeri dispari:

var isOdd = x => x% 2! == 0;
numeri var = [1, 2, 3, 4, 5];
var oddNumbers = filtro (isOdd, numeri);
) (console.log oddNumbers; // [1, 3, 5]

L'uso della nostra nuova funzione di filtro è molto più semplice rispetto alla codifica manuale con un for-loop.

L'ultima funzione comune si chiama ridurre. In genere, viene utilizzato per prendere un elenco e ridurlo a un singolo valore, ma in realtà può fare molto di più.

Questa funzione è generalmente chiamata fold in Linguaggi funzionali.

var reduce = (f, start, array) => {
    var acc = start;
    per (var i = 0; i 

La funzione di riduzione accetta una funzione di riduzione, f, un valore iniziale iniziale e un array.

Si noti che la funzione di riduzione, f, accetta 2 parametri, l'elemento corrente dell'array e l'accumulatore, acc. Utilizzerà questi parametri per produrre un nuovo accumulatore ogni iterazione. Viene restituito l'accumulatore dall'iterazione finale.

Un esempio ci aiuterà a capire come funziona:

var add = (x, y) => x + y;
valori var = [1, 2, 3, 4, 5];
var sumOfValues ​​= riduci (aggiungi, 0, valori);
console.log (sumOfValues); // 15

Si noti che la funzione di aggiunta accetta 2 parametri e li aggiunge. La nostra funzione di riduzione prevede una funzione che accetta 2 parametri in modo che funzionino bene insieme.

Iniziamo con un valore iniziale pari a zero e passiamo al nostro array, valori da sommare. All'interno della funzione di riduzione, la somma viene accumulata man mano che scorre sui valori. Il valore finale accumulato viene restituito come sumOfValues.

Ognuna di queste funzioni, mappa, filtro e riduzione ci consente di eseguire operazioni di manipolazione comuni sugli array senza dover scrivere for-loop per il plateplate.

Ma nei linguaggi funzionali, sono ancora più utili poiché non ci sono costrutti loop solo ricorsione. Le funzioni di iterazione non sono solo estremamente utili. Sono necessarie.

Il mio cervello!!!!

Per ora basta.

Nelle parti successive di questo articolo, parlerò di integrità referenziale, ordine di esecuzione, tipi e altro.

Avanti: Parte 5

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