The way the "anemic model" problem is described doesn't translate well to FP as is. First it needs to be suitably generalized. At it's heart, an anemic model is a model which contains knowledge about how to properly use it that isn't encapsulated by the model itself. Instead, that knowledge is spread around a pile of related services. Those services should only be clients of the model, but due to its anemia they're held responsible for it. For example, consider an Account
class that can't be used to activate or deactivate accounts or even lookup information about an account unless handled via an AccountManager
class. The account should be responsible for basic operations on it, not some external manager class.
In functional programming, a similar problem exists when data types don't accurately represent what they're supposed to model. Suppose we need to define a type representing user IDs. An "anemic" definition would state that user IDs are strings. That's technically feasible, but runs into huge problems because user IDs aren't used like arbitrary strings. It makes no sense to concatenate them or slice out substrings of them, Unicode shouldn't really matter, and they should be easily embeddable in URLs and other contexts with strict character and format limitations.
Solving this problem usually happens in a few stages. A simple first cut is to say "Well, a UserID
is represented equivalently to a string, but they're different types and you can't use one where you expect the other." Haskell (and some other typed functional languages) provides this feature via newtype
:
newtype UserID = UserID String
This defines a UserID
function which when given a String
constructs a value that is treated like a UserID
by the type system, but which is still just a String
at runtime. Now functions can declare that they require a UserID
instead of a string; using UserID
s where you previously were using strings guards against code concatenating two UserID
s together. The type system guarantees that can't happen, no tests required.
The weakness here is that code can still take any arbitrary String
like "hello"
and construct a UserID
from it. Further steps include creating a "smart constructor" function which when given a string checks some invariants and only returns a UserID
if they're satisfied. Then the "dumb" UserID
constructor is made private so if a client wants a UserID
they must use the smart constructor, thereby preventing malformed UserIDs from coming into existence.
Even further steps define the UserID
data type in such a way that it's impossible to construct one that's malformed or "improper", simply by definition. For instance, defining a UserID
as a list of digits:
data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]
To construct a UserID
a list of digits must be provided. Given this definition, it's trivial to show that it's impossible for a UserID
to exist that can't be represented in a URL. Defining data models like this in Haskell is often aided by advanced type system features like Data Kinds and Generalized Algebraic Data Types (GADTs), which allow the type system to define and prove more invariants about your code. When data is decoupled from behavior your data definition is the only means you have to enforce behavior.