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