Те, як описана проблема "анемічної моделі", не відповідає успіху FP, як є. По-перше, це потрібно відповідним чином узагальнити. По суті, анемічна модель - це модель, яка містить знання про те, як правильно її використовувати, а не інкапсульована самою моделлю. Натомість ці знання поширюються навколо купи супутніх послуг. Ці служби повинні бути лише клієнтами моделі, але через анемію вони несуть відповідальність за неї. Наприклад, розглянемо Account
клас, який не можна використовувати для активації чи деактивації облікових записів або навіть пошуку інформації про обліковий запис, якщо не обробляється через AccountManager
клас. Обліковий запис повинен відповідати за основні операції над ним, а не якийсь зовнішній клас менеджера.
У функціональному програмуванні подібна проблема існує, коли типи даних не представляють точно те, що вони повинні моделювати. Припустимо, нам потрібно визначити тип, що представляє ідентифікатори користувачів. "Анемічне" визначення зазначає, що ідентифікатори користувачів - це рядки. Це технічно можливо, але виникає величезна проблема, оскільки ідентифікатори користувачів не використовуються як довільні рядки. Немає сенсу об'єднувати їх або вирізати їх підрядки, Unicode насправді не має значення, і вони повинні легко вбудовуватися в URL-адреси та інші контексти із суворими обмеженнями символів та форматів.
Вирішення цієї проблеми зазвичай відбувається в кілька етапів. Простий перший розріз - сказати "Ну, а UserID
представлений еквівалентно рядку, але вони різні типи, і ви не можете використовувати одне там, де очікуєте іншого". Haskell (та деякі інші набрані функціональні мови) надають цю функцію через newtype
:
newtype UserID = UserID String
Це визначає UserID
функцію , яка , коли задана String
будує значення, яке обробляє , якUserID
система типу, але все ще тільки String
під час виконання. Тепер функції можуть оголосити, що вони вимагають UserID
замість рядка; використання UserID
s, де ви раніше використовували захисні рядки проти коду, що об'єднує два UserID
s разом. Система типу гарантує, що цього не може відбутися, ніяких тестів не потрібно.
Слабкість в тому , що код все ще може приймати будь-яке довільне , String
як "hello"
і побудувати UserID
з нього. Подальші кроки включають створення функції "розумного конструктора", яка при введенні рядка перевіряє деякі інваріанти і повертає лише те, UserID
якщо вони задоволені. Тоді "німий" UserID
конструктор стає приватним, тому, якщо клієнт хоче, UserID
він повинен використовувати розумний конструктор, тим самим запобігаючи появі неправильно сформованих UserID.
Навіть подальші кроки визначають UserID
тип даних таким чином, що неможливо побудувати той, який неправильно сформований або "неправильний", просто за визначенням. Наприклад, визначення a UserID
як списку цифр:
data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]
Для побудови UserID
списку цифр необхідно надати. З огляду на це визначення, неправдиво показати, що UserID
існування неможливо, яке не може бути представлене в URL-адресі. Визначення подібних моделей даних у Haskell часто допомагає розширеним функціям системи типу, таким як Data Data та General Algebraic Data Types (GADT) , які дозволяють системі типів визначати та доводити більше інваріантів щодо вашого коду. Коли дані відокремлюються від поведінки, визначення даних є єдиним засобом, який потрібно застосовувати до поведінки.