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 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
UserIDs where you previously were using strings guards against code concatenating two
UserIDs together. The type system guarantees that can't happen, no tests required.
The weakness here is that code can still take any arbitrary
"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.