Il modo in cui viene descritto il problema del "modello anemico" non si traduce bene in FP così com'è. Innanzitutto deve essere adeguatamente generalizzato. Al suo centro, un modello anemico è un modello che contiene conoscenze su come usarlo correttamente che non è incapsulato dal modello stesso. Invece, quella conoscenza è diffusa in un mucchio di servizi correlati. Tali servizi dovrebbero essere solo clienti del modello, ma a causa della sua anemia ne sono ritenuti responsabili . Ad esempio, considera una Account
classe che non può essere utilizzata per attivare o disattivare gli account o persino cercare informazioni su un account se non gestita tramite una AccountManager
classe. L'account dovrebbe essere responsabile delle operazioni di base su di esso, non di alcune classi manager esterne.
Nella programmazione funzionale, esiste un problema simile quando i tipi di dati non rappresentano accuratamente ciò che dovrebbero modellare. Supponiamo di dover definire un tipo che rappresenti gli ID utente. Una definizione "anemica" indicherebbe che gli ID utente sono stringhe. È tecnicamente fattibile, ma si verificano enormi problemi perché gli ID utente non vengono utilizzati come stringhe arbitrarie. Non ha senso concatenarli o suddividere le loro sottostringhe, Unicode non dovrebbe davvero importare, e dovrebbero essere facilmente incorporabili in URL e altri contesti con rigide limitazioni di carattere e formato.
La risoluzione di questo problema si verifica in genere in alcune fasi. Un semplice primo taglio è dire "Beh, a UserID
è rappresentato in modo equivalente a una stringa, ma sono tipi diversi e non puoi usarne uno dove ti aspetti l'altro". Haskell (e alcuni altri linguaggi funzionali digitati) fornisce questa funzione tramite newtype
:
newtype UserID = UserID String
Questo definisce una UserID
funzione che quando viene data String
costruisce un valore che viene trattato come un UserID
sistema di tipo, ma che è ancora solo String
in fase di esecuzione. Ora le funzioni possono dichiarare che richiedono UserID
una stringa anziché una stringa; usando UserID
s dove in precedenza si utilizzavano protezioni stringhe contro il codice che concatena due UserID
s insieme. Il sistema di tipi garantisce che ciò non può avvenire, non sono richiesti test.
Il punto debole qui è che il codice può ancora prendere qualsiasi String
like arbitrario "hello"
e costruirne uno UserID
. Ulteriori passaggi includono la creazione di una funzione "costruttore intelligente" che quando viene data una stringa controlla alcuni invarianti e restituisce a solo UserID
se sono soddisfatti. Poi il "muto" UserID
costruttore è fatto in modo privato, se un cliente vuole un UserID
essi devono utilizzare il costruttore intelligente, impedendo in tal modo UserIDs malformati da venire all'esistenza.
Anche ulteriori passaggi definiscono il UserID
tipo di dati in modo tale che sia impossibile costruirne uno malformato o "improprio", semplicemente per definizione. Ad esempio, definendo a 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
elenco di cifre deve essere fornito. Data questa definizione, è banale dimostrare che è impossibile UserID
che esista un 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 tipi di dati e tipi di dati algebrici generalizzati (GADT) , che consentono al sistema di tipi di definire e dimostrare più invarianti sul codice. Quando i dati sono disaccoppiati dal comportamento, la definizione dei dati è il solo mezzo per imporre il comportamento.