La façon dont le problème de "modèle anémique" est décrit ne se traduit pas bien en PF en l'état. Tout d'abord, il doit être généralisé de manière appropriée. En son cœur, un modèle anémique est un modèle qui contient des connaissances sur la manière de l’utiliser correctement qui n’est pas encapsulé par le modèle lui-même. Au lieu de cela, ces connaissances sont réparties autour d’une pile de services connexes. Ces services ne devraient être que des clients du modèle, mais en raison de son anémie, ils en sont tenus responsables . Par exemple, considérons une Accountclasse qui ne peut pas être utilisée pour activer ou désactiver des comptes ou même rechercher des informations sur un compte si elle n'est pas gérée via une AccountManagerclasse. Le compte doit être responsable des opérations de base sur celui-ci, et non d'une classe de gestionnaire externe.
En programmation fonctionnelle, un problème similaire existe lorsque les types de données ne représentent pas exactement ce qu'ils sont censés modéliser. Supposons que nous devions définir un type représentant les ID utilisateur. Une définition "anémique" indiquerait que les ID utilisateur sont des chaînes. C'est techniquement faisable, mais pose d'énormes problèmes car les ID utilisateur ne sont pas utilisés comme des chaînes arbitraires. Cela n'a aucun sens de les concaténer ou d'en découper des sous-chaînes. Unicode ne devrait pas avoir d'importance, et ils devraient pouvoir être incorporés facilement dans des URL et d'autres contextes avec des limitations strictes de caractères et de format.
La résolution de ce problème se produit généralement en quelques étapes. Une première coupe simple consiste à dire "Eh bien, a UserIDest représenté de manière équivalente à une chaîne, mais ils sont de types différents et vous ne pouvez pas utiliser l'un de ceux que vous attendez de l'autre." Haskell (et quelques autres langages fonctionnels dactylographiés) fournit cette fonctionnalité via newtype:
newtype UserID = UserID String
Ceci définit une UserIDfonction qui, lorsqu'elle reçoit une Stringvaleur, construit une valeur qui est traitée comme un UserIDsystème de types, mais qui reste juste Stringà l'exécution. Désormais, les fonctions peuvent déclarer qu’elles nécessitent un à la UserIDplace d’une chaîne; en utilisant UserIDs où vous utilisiez auparavant des chaînes, gardes contre le code concaténant deux UserIDs ensemble. Le système de types garantit que cela ne peut pas arriver, aucun test requis.
La faiblesse est que le code peut encore prendre toute arbitraire Stringcomme "hello"et construire un UserIDde lui. La prochaine étape consiste à créer une fonction "constructeur intelligent" qui, lorsqu'elle reçoit une chaîne, vérifie certains invariants et ne les renvoie que UserIDs'ils sont satisfaits. Ensuite, le UserIDconstructeur "dumb" est rendu privé. Par conséquent, si un client le souhaite, UserIDil doit utiliser le constructeur intelligent, empêchant ainsi la création d’identificateurs d’utilisateur mal formés.
Même les étapes suivantes définissent le UserIDtype de données de telle sorte qu'il est impossible d'en construire un qui est mal formé ou "impropre", par simple définition. Par exemple, définir UserIDune liste de chiffres:
data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]
Pour construire une UserIDliste de chiffres doit être fournie. Compte tenu de cette définition, il est trivial de montrer qu'il est impossible UserIDd'exister sans pouvoir être représenté dans une URL. Définir de tels modèles de données dans Haskell est souvent facilité par des fonctionnalités de système de types avancées telles que les types de données et les types de données algébriques généralisées (GADT) , qui permettent au système de types de définir et de prouver davantage d'invariants dans votre code. Lorsque les données sont découplées du comportement, votre définition de données est le seul moyen dont vous disposez pour appliquer le comportement.