Il modo in cui il problema del "modello anemico" è descritto non si traduce bene con FP come è. Per prima cosa deve essere adeguatamente generalizzato. Nel suo cuore, un modello anemico è un modello che contiene conoscenze su come usarlo correttamente e non è incapsulato dal modello stesso. Invece, quella conoscenza si sviluppa attorno a una pila di servizi correlati. Questi servizi dovrebbero essere solo client del modello, ma a causa della sua anemia sono ritenuti responsabili per questo. Ad esempio, considera una classe Account
che non può essere utilizzata per attivare o disattivare account o anche informazioni di ricerca su un account a meno che non venga gestita tramite una classe AccountManager
. L'account dovrebbe essere responsabile delle operazioni di base su di esso, non di alcune classi di manager esterni.
Nella programmazione funzionale, esiste un problema simile quando i tipi di dati non rappresentano esattamente ciò che dovrebbero modellare. Supponiamo di dover definire un tipo che rappresenti gli ID utente. Una definizione "anemica" indica che gli ID utente sono stringhe. Questo è tecnicamente fattibile, ma presenta enormi problemi perché gli ID utente non sono utilizzati come stringhe arbitrarie. Non ha senso concatenarli o sviscerarli, Unicode non dovrebbe avere molta importanza e dovrebbero essere facilmente incorporabili in URL e in altri contesti con limiti rigorosi di carattere e formato.
La risoluzione di questo problema di solito avviene in alcune fasi. Un semplice primo taglio è dire "Beh, un UserID
è rappresentato in modo equivalente a una stringa, ma sono tipi diversi e non puoi usarne uno dove ti aspetti l'altro." Haskell (e alcune altre lingue funzionali digitate) fornisce questa funzione tramite newtype
:
newtype UserID = UserID String
Definisce una funzione UserID
che quando viene data una String
costruisce un valore che è trattato come a UserID
dal sistema di tipi, ma che è ancora solo un String
a runtime. Ora le funzioni possono dichiarare che richiedono un UserID
invece di una stringa; usando UserID
s in cui in precedenza utilizzavi le guardie delle stringhe contro il codice che concatena due UserID
s insieme. Il sistema di tipi garantisce che non può accadere, nessun test richiesto.
Il punto debole è che il codice può ancora prendere qualsiasi String
arbitrario come "hello"
e costruire un UserID
da esso. Ulteriori passaggi includono la creazione di una funzione "costruttore intelligente" che, quando viene fornita una stringa, verifica alcuni invarianti e restituisce solo UserID
se sono soddisfatti. Quindi il costruttore "stupido" UserID
viene reso privato, quindi se un cliente desidera un UserID
, deve utilizzare il costruttore intelligente, impedendo così l'esistenza di UserID non validi.
Anche ulteriori passaggi definiscono il tipo di dati UserID
in modo che sia impossibile per costruirne uno che è malformato o "improprio", semplicemente per definizione. Ad esempio, definendo un UserID
come un elenco di cifre:
data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]
Per costruire un UserID
deve essere fornito un elenco di cifre. Data questa definizione, è banale mostrare che è impossibile che esista un UserID
che non può essere rappresentato in un URL. La definizione di modelli di dati come questo in Haskell è spesso aiutata da funzionalità di sistema di tipo avanzato come Dati Tipi e Tipi di dati algebrici generalizzati (GADT) , che consentono al sistema di tipi di definire e dimostrare più invarianti sul tuo codice . Quando i dati vengono disaccoppiati dal comportamento, la definizione dei dati è l'unico mezzo per forzare il comportamento.