La gestione delle dipendenze è un grosso problema in OOP per i seguenti due motivi:
- Lo stretto accoppiamento di dati e codice.
- Uso onnipresente di effetti collaterali
La maggior parte dei programmatori OO ritiene che l'accoppiamento stretto di dati e codice sia del tutto vantaggioso, ma comporta un costo. Gestire il flusso di dati attraverso i livelli è una parte inevitabile della programmazione in ogni paradigma. Accoppiamento i dati e codice aggiunge l'ulteriore problema che, se si desidera utilizzare una funzione di a un certo punto, si deve trovare un modo ottenere il suo oggetto a quel punto.
L'uso di effetti collaterali crea difficoltà simili. Se si utilizza un effetto collaterale per alcune funzionalità, ma si vuole essere in grado di sostituirne l'implementazione, praticamente non si ha altra scelta che quella di iniettare tale dipendenza.
Considerare come esempio un programma di spammer che gratta le pagine Web per gli indirizzi e-mail, quindi li invia per e-mail. Se hai una mentalità DI, in questo momento stai pensando ai servizi che incapsulerai dietro le interfacce e quali servizi verranno iniettati dove. Lascerò quel disegno come un esercizio per il lettore. Se hai una mentalità FP, in questo momento stai pensando agli input e output per il livello più basso di funzioni, come:
- Inserisci un indirizzo di una pagina web, genera il testo di quella pagina.
- Inserisci il testo di una pagina, genera un elenco di link da quella pagina.
- Inserisci il testo di una pagina, genera un elenco di indirizzi email su quella pagina.
- Inserisci un elenco di indirizzi email, genera un elenco di indirizzi email con i duplicati rimossi.
- Inserisci un indirizzo email, invia un'email di spam per quell'indirizzo.
- Inserisci un messaggio di spam, invia i comandi SMTP per inviare quell'email.
Quando si pensa in termini di input e output, non ci sono dipendenze di funzioni, solo dipendenze dei dati. Questo è ciò che li rende così facili da testare l'unità. Il tuo livello successivo organizza l'output di una funzione da inserire nell'input del successivo e può facilmente scambiare le varie implementazioni secondo necessità.
In senso molto reale, la programmazione funzionale ti spinge naturalmente a invertire sempre le dipendenze tra le funzioni, e di conseguenza non devi prendere misure speciali per farlo dopo il fatto. Quando lo fai, strumenti come le funzioni di ordine superiore, le chiusure e l'applicazione parziale rendono più facile l'esecuzione con una quantità minore di boilerplate.
Si noti che non sono le stesse dipendenze a essere problematiche. Sono le dipendenze che indicano la strada sbagliata. Il livello successivo può avere una funzione come:
processText = spamToSMTP . emailAddressToSpam . removeEmailDups . textToEmailAddresses
Va perfettamente bene per questo strato avere dipendenze codificate in questo modo, perché il suo unico scopo è quello di incollare insieme le funzioni di livello inferiore. Scambiare un'implementazione è semplice come creare una composizione diversa:
processTextFancy = spamToSMTP . emailAddressToFancySpam . removeEmailDups . textToEmailAddresses
Questa facile ricomposizione è resa possibile dalla mancanza di effetti collaterali. Le funzioni di livello inferiore sono completamente indipendenti l'una dall'altra. Il prossimo livello in alto può scegliere quale% diprocessText
viene effettivamente utilizzato in base a qualche configurazione utente:
actuallyUsedProcessText = if (config == "Fancy") then processTextFancy else processText
Ancora una volta, non un problema perché tutte le dipendenze puntano in un modo. Non è necessario invertire alcune dipendenze per farle puntare tutte allo stesso modo, perché le funzioni pure ci hanno già obbligato a farlo.
Nota che potresti renderlo molto più accoppiato passando config
fino al livello più basso invece di controllarlo in alto. FP non ti impedisce di farlo, ma tende a renderlo molto più fastidioso se provi.