Sposób, w jaki opisany jest problem „modelu anemicznego”, nie przekłada się dobrze na FP. Najpierw należy go odpowiednio uogólnić. Sercem modelu anemicznego jest model, który zawiera wiedzę o tym, jak właściwie go używać, który nie jest zamknięty w samym modelu. Zamiast tego wiedza ta rozprzestrzenia się na stos powiązanych usług. Usługi te powinny być tylko klientami tego modelu, ale z powodu anemii ponoszą za to odpowiedzialność . Rozważmy na przykład Account
klasę, której nie można użyć do aktywacji lub dezaktywacji kont, a nawet wyszukiwania informacji o koncie, chyba że są obsługiwane przez AccountManager
klasę. Konto powinno być odpowiedzialne za podstawowe operacje na nim, a nie niektóre zewnętrzne klasy menedżerów.
W programowaniu funkcjonalnym podobny problem występuje, gdy typy danych nie odzwierciedlają dokładnie tego, co powinny modelować. Załóżmy, że musimy zdefiniować typ reprezentujący identyfikatory użytkownika. Definicja „anemiczna” oznaczałaby, że identyfikatory użytkowników są łańcuchami. Jest to technicznie wykonalne, ale napotyka ogromne problemy, ponieważ identyfikatory użytkowników nie są używane jak ciągi arbitralne. Nie ma sensu ich łączenia ani wycinania podciągów, Unicode nie powinien mieć większego znaczenia i powinny być łatwe do osadzenia w adresach URL i innych kontekstach o ściśle ograniczonych znakach i formatach.
Rozwiązanie tego problemu zwykle przebiega w kilku etapach. Prostym pierwszym cięciem jest powiedzenie: „Cóż, a UserID
jest reprezentowane równorzędnie z łańcuchem, ale są to różne typy i nie można użyć jednego tam, gdzie oczekujesz drugiego”. Haskell (i niektóre inne wpisane języki funkcjonalne) udostępnia tę funkcję poprzez newtype
:
newtype UserID = UserID String
To definiuje UserID
funkcję, która gdy dali String
konstruuje wartość, która jest traktowana jakUserID
przez system typu, ale jest nadal tylko String
przy starcie. Teraz funkcje mogą zadeklarować, że wymagają UserID
zamiast ciągu znaków; używając UserID
s, gdzie wcześniej używałeś łańcuchów chroniących przed kodem UserID
łączącym dwa s razem. System typów gwarantuje, że to się nie stanie, nie są wymagane żadne testy.
Słabością jest to, że kod może nadal brać dowolnej String
jak "hello"
i zbudować UserID
z niego. Dalsze kroki obejmują utworzenie „inteligentnego konstruktora”, który po podaniu łańcucha sprawdza niektóre niezmienniki i zwraca tylko, UserID
jeśli są spełnione. Następnie „głupi” UserID
konstruktor zostaje ustawiony na prywatny, więc jeśli klient chce UserID
, musi użyć inteligentnego konstruktora, zapobiegając w ten sposób powstaniu zniekształconych identyfikatorów użytkowników.
Nawet dalsze kroki definiują UserID
typ danych w taki sposób, że niemożliwe jest zbudowanie takiego, który jest zniekształcony lub „niewłaściwy”, po prostu z definicji. Na przykład zdefiniowanie UserID
jako listy cyfr:
data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]
Aby zbudować UserID
listę cyfr, należy podać. Biorąc pod uwagę tę definicję, trywialne jest pokazanie, że UserID
istnienie, którego nie można przedstawić w adresie URL, jest niemożliwe . Definiowanie modeli danych, jak to w Haskell jest często wspomagane przez zaawansowanych funkcji systemowych, takich jak typ Rodzaje danych i typy uogólnione dane (GADTs algebraiczne) , które pozwalają na system typu zdefiniować i udowodnić więcej niezmienników o kodzie. Gdy dane są oddzielone od zachowania, definicja danych jest jedynym sposobem wymuszenia zachowania.