A maneira como o problema do "modelo anêmico" é descrito não se traduz bem no FP como está. Primeiro, ele precisa ser adequadamente generalizado. No fundo, um modelo anêmico é um modelo que contém conhecimento sobre como usá-lo adequadamente, que não é encapsulado pelo próprio modelo. Em vez disso, esse conhecimento está espalhado em torno de uma pilha de serviços relacionados. Esses serviços devem ser apenas clientes do modelo, mas devido à sua anemia, eles são responsabilizados por ele. Por exemplo, considere uma Account
classe que não pode ser usada para ativar ou desativar contas ou mesmo procurar informações sobre uma conta, a menos que seja tratada por meio de uma AccountManager
classe. A conta deve ser responsável por operações básicas, não por alguma classe de gerente externo.
Na programação funcional, existe um problema semelhante quando os tipos de dados não representam com precisão o que devem modelar. Suponha que precisamos definir um tipo representando IDs do usuário. Uma definição "anêmica" indicaria que os IDs do usuário são cadeias. Isso é tecnicamente viável, mas enfrenta grandes problemas porque os IDs de usuário não são usados como sequências arbitrárias. Não faz sentido concatená-los ou cortar subcadeias deles, o Unicode não deve realmente importar e deve ser facilmente incorporado em URLs e outros contextos com limitações estritas de caracteres e formatos.
A solução desse problema geralmente ocorre em algumas etapas. Um primeiro corte simples é dizer "Bem, a UserID
é representado equivalentemente a uma string, mas são tipos diferentes e você não pode usar um onde espera o outro". Haskell (e algumas outras linguagens funcionais digitadas) fornece esse recurso via newtype
:
newtype UserID = UserID String
Isso define uma UserID
função que, quando fornecida, String
constrói um valor que é tratado como um UserID
pelo sistema de tipos, mas que ainda é apenas um String
em tempo de execução. Agora, as funções podem declarar que exigem UserID
uma sequência em vez de uma sequência; usando UserID
s em que você estava usando cordas guardas contra o código que concatena duasUserID
s juntos. O sistema de tipos garante que isso não pode acontecer, sem a necessidade de testes.
A fraqueza aqui é que o código ainda pode pegar qualquer String
tipo arbitrário "hello"
e construir um a UserID
partir dele. Outras etapas incluem a criação de uma função "construtor inteligente" que, quando recebe uma string, verifica alguns invariantes e retorna apenas um UserID
se estiver satisfeito. Em seguida, o UserID
construtor "burro" é tornado privado, portanto, se um cliente quiser umUserID
ele deve usar o construtor inteligente, impedindo que IDs de usuário malformados passem a existir.
Outras etapas definem o UserID
tipo de dados de tal maneira que é impossível construir um que esteja malformado ou "impróprio", simplesmente por definição. Por exemplo, definindo a UserID
como uma lista de dígitos:
data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]
Para construir uma UserID
lista de dígitos deve ser fornecido. Dada essa definição, é trivial mostrar que é impossível UserID
existir um que não possa ser representado em um URL. A definição de modelos de dados como este em Haskell geralmente é auxiliada por recursos avançados do sistema de tipos, como tipos de dados e tipos de dados algébricos generalizados (GADTs) , que permitem ao sistema de tipos definir e provar mais invariantes sobre seu código. Quando os dados são dissociados do comportamento, sua definição de dados é o único meio de você impor o comportamento.