È possibile evitare la programmazione funzionale come politica?

Politica di Nick Youngson CC BY-SA 3.0 Alpha Immagini Stock

La programmazione funzionale è penetrata nella maggior parte del mondo della programmazione tradizionale - gran parte dell'ecosistema JS, Linq per C #, anche funzioni di ordine superiore in Java. Questo è Java nel 2018:

getUserName (utenti, utente -> user.getUserName ());

FP è così utile e conveniente, che ha trovato la sua strada in ogni linguaggio di programmazione moderno nell'uso tradizionale che mi viene in mente.

Ma non sono tutti arcobaleni e cuccioli. Molti sviluppatori stanno lottando con questo cambiamento tettonico nel modo in cui pensiamo al software. Francamente, oggi è difficile trovare un lavoro JavaScript che non richieda un certo conforto con i concetti di programmazione funzionale.

FP è al centro di entrambi i framework dominanti, React (evitare il DOM mutevole condiviso è la motivazione per la sua architettura e il flusso di dati a 1 via) e Angular (RxJS è una libreria di operatori di utility che agiscono sui flussi attraverso funzioni di ordine - è ampiamente utilizzato in tutta Angular). Redux e ngrx / store pure: funzionante su entrambi i fronti.

La programmazione funzionale può essere un argomento intimidatorio da affrontare per gli sviluppatori che ne sono nuovi e, nell'interesse di un rapido onboarding, qualcuno nel tuo team potrebbe suggerire di rendere più facile per il tuo team adattarsi alla base di codice evitando FP come politica.

Per i manager che non hanno familiarità con ciò che è FP o la sua pervasività nell'ecosistema di codice moderno, ciò potrebbe effettivamente sembrare un suggerimento sano. Dopotutto, OOP non ha servito bene il mondo del software per 30 anni? Perché non continuare a farlo?

Divertiamoci per un momento.

Cosa significherebbe anche "vietare" il FP come politica?

Cos'è la programmazione funzionale?

La mia definizione preferita di programmazione funzionale è questa:

La programmazione funzionale è un paradigma di programmazione che utilizza funzioni pure come unità atomica di composizione, evitando stati mutabili condivisi ed effetti collaterali.

Una funzione pura è una funzione che:

  • Dato lo stesso input, restituisce sempre lo stesso output
  • Non ha effetti collaterali

L'essenza di FP si riduce davvero a:

  • Programma con funzioni
  • Evita lo stato mutevole condiviso e gli effetti collaterali

Quando li metti insieme, ottieni lo sviluppo del software usando le funzioni pure come i tuoi blocchi costitutivi (l '"unità atomica di composizione").

Per inciso, secondo Alan Kay, l'istigatore di tutto il moderno OOP, l'essenza di OOP è:

  • incapsulamento
  • Passaggio dei messaggi

Quindi OOP è solo un altro approccio per evitare lo stato mutevole condiviso e gli effetti collaterali.

Chiaramente, l'opposto di FP non è OOP. L'opposto di FP è la programmazione procedurale non strutturata.

Smalltalk (dove Alan Kay ha gettato le basi di OOP) è sia orientato agli oggetti che funzionale, e l'idea di scegliere l'uno o l'altro è completamente estranea e impensabile.

Lo stesso vale per JavaScript. Quando Brendan Eich fu assunto per costruire JavaScript, le idee erano:

  • Schema nel browser (FP)
  • Assomiglia a Java (OOP)

In JavaScript, puoi provare a favorire l'uno o l'altro, ma nel bene o nel male, sono indissolubilmente collegati. L'incapsulamento in JavaScript si basa su chiusure - un concetto dalla programmazione funzionale.

È molto probabile che tu stia già facendo una programmazione funzionale, che tu lo sappia o no.

Come NON fare FP:

Per evitare la programmazione funzionale, dovresti evitare di usare funzioni pure. Il problema è che ora non puoi scriverlo perché potrebbe essere puro:

const getName = obj => obj.name;
const name = getName ({uid: '123', nome: 'Banksy'}); // Banksy

Riflettiamo su questo, quindi non è più FP. Potremmo renderlo una classe con una proprietà pubblica, invece. Dal momento che non utilizza l'incapsulamento, è un allungamento chiamare questo OOP. Forse dovremmo chiamare questa "programmazione procedurale di oggetti" ?:

utente di classe {
  costruttore ({nome}) {
    this.name = name;
  }
  getName () {
    restituire this.name;
  }
}
const myUser = nuovo utente ({uid: '123', nome: 'Banksy'});
const name = myUser.getName (); // Banksy

Congratulazioni! Abbiamo appena trasformato 2 righe di codice in 11 e introdotto la probabilità di mutazione esterna incontrollata. Cosa abbiamo guadagnato?

Beh, niente, davvero. In effetti, abbiamo perso molta flessibilità e il potenziale per significativi risparmi di codice.

La precedente funzione getName () ha funzionato con qualsiasi oggetto di input. Anche questo (perché è JavaScript e possiamo delegare metodi a oggetti casuali), ma è molto più imbarazzante. Potremmo lasciare che le due classi ereditino da una classe base comune, ma ciò implica relazioni che potrebbero non aver bisogno di esistere tra le classi di oggetti.

Dimentica la riusabilità. L'abbiamo appena scaricato nello scarico. Ecco come si presenta la duplicazione del codice:

class Friend {
  costruttore ({nome}) {
    this.name = name;
  }
  getName () {
    restituire this.name;
  }
}

Un utile studente entra dal retro della stanza:

"Crea una classe personale, ovviamente!"

Ma allora:

classe Paese {
  costruttore ({nome}) {
    this.name = name;
  }
  getName () {
    restituire this.name;
  }
}
“Ma ovviamente questi sono tipi diversi. Ovviamente non puoi usare un metodo personale in un Paese! "

A cui rispondo: "Perché no?"

Uno dei sorprendenti vantaggi della programmazione funzionale è la programmazione generica banalmente facile. Potresti chiamarlo "generico per impostazione predefinita": la possibilità di scrivere una singola funzione che funziona con qualsiasi tipo che soddisfi i suoi requisiti generici.

Nota: per le persone Java, non si tratta di tipi statici. Alcuni linguaggi FP hanno ottimi sistemi di tipo statico e condividono ancora questo vantaggio utilizzando funzionalità come tipi strutturali e / o di tipo superiore.

Questo esempio è banale, ma i risparmi nel volume di codice che otteniamo da quel trucco sono monumentali.

Consente a librerie come autodux di generare automaticamente la logica di dominio per qualsiasi oggetto composto da coppie getter / setter (e molto altro, oltre). Questo trucco da solo può dimezzare o meglio il codice per la logica del tuo dominio.

Niente più funzioni di ordine superiore

Poiché la maggior parte (ma non tutte) le funzioni di ordine superiore sfruttano le funzioni pure per restituire gli stessi valori in base agli stessi input, non è possibile utilizzare funzioni come .map (), .filter (), reduce () senza lanciando un effetto collaterale, solo per dire che non sei puro:

const arr = [1,2,3];
const double = n => n * 2;
const raddoppiatoArr = arr.map (doppio);

diventa:

const arr = [1,2,3];
const double = (n, i) => {
  console.log ('Effetti collaterali casuali senza motivo.');
  console.log (`Oh, lo so, potremmo salvare direttamente l'output
al database e accoppia strettamente la nostra logica di dominio al nostro I / O. Andrà bene. Nessun altro dovrà moltiplicarsi per 2, giusto? `);
  saveToDB (i, n);
  ritorno n * 2;
};
const raddoppiatoArr = arr.map (doppio);

RIP, composizione delle funzioni 1958–2018

Dimentica la composizione senza punti dei componenti di ordine superiore per incapsulare le tue preoccupazioni trasversali tra le pagine. Questa sintassi comoda e dichiarativa è ora vietata:

const wrapEveryPage = compose (
  withRedux,
  withEnv,
  withLoader,
  withTheme,
  withLayout,
  withFeatures ({initialFeatures})
);

Dovrai importare manualmente ognuno di questi in ogni componente, o peggio ancora - scendere in una gerarchia intricata e inflessibile di eredità di classe (giustamente considerata un anti-modello dalla maggior parte del mondo sano, anche [specialmente?] Dal canone di progettazione OO ).

Addio, promesse e asincrono / attendono

Le promesse sono monadi. Tecnicamente dalla teoria delle categorie, ma ho sentito che anche loro sono una cosa del FP, perché Haskell li possiede e usa le monadi per rendere tutto puro e pigro.

Onestamente, perdere tutorial monade e functor è probabilmente una buona cosa. Queste cose sono molto più facili di quanto possiamo farle sembrare! C'è una ragione per cui insegno alle persone come usare Array.prototype.map e le promesse prima di introdurre i concetti generali di funzioni e monadi.

Sai come usarli? Sei più che a metà strada per comprendere i funzioni e le monadi.

Quindi, per evitare FP

  • Non utilizzare nessuno dei framework o librerie più popolari di JavaScript (ti tradiranno tutti in FP!).
  • Non scrivere funzioni pure.
  • Non utilizzare molte delle funzionalità integrate di JavaScript: la maggior parte delle funzioni matematiche (perché sono pure), metodi di stringa e array non mutanti, .map (), .filter (), .forEach (), promesse, o asincrono / attendono.
  • Scrivi classi non necessarie.
  • Raddoppia (o più) la tua logica di dominio scrivendo manualmente getter e setter per letteralmente tutto.
  • Adotta l'approccio imperativo "leggibile, esplicito" e aggroviglia la logica del tuo dominio con problemi di rendering e I / O di rete.

E saluta:

  • Debug dei viaggi nel tempo
  • Facile sviluppo delle funzionalità di annullamento / ripetizione
  • Prove unitarie solide e costanti
  • Test gratuiti Mock e D / I
  • Test di unità rapidi senza dipendenze dall'I / O di rete
  • Basi di codice piccole, verificabili, debuggabili e gestibili

Evitare la programmazione funzionale come politica? Nessun problema.

Oh aspetta.

Maggiori informazioni su EricElliottJS.com

Le lezioni video sulla programmazione funzionale sono disponibili per i membri di EricElliottJS.com. Se non sei un membro, iscriviti oggi.

Eric Elliott è autore di "Programmazione di applicazioni JavaScript" (O’Reilly) e cofondatore della piattaforma di tutoraggio software, DevAnywhere.io. Ha contribuito alle esperienze software per Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC e i migliori artisti della registrazione tra cui Usher, Frank Ocean, Metallica e molti altri.

Lavora da remoto ovunque con la donna più bella del mondo.