La forma en que se describe el problema del "modelo anémico" no se traduce bien en FP como es. Primero necesita ser adecuadamente generalizado. En esencia, un modelo anémico es un modelo que contiene conocimiento sobre cómo usarlo adecuadamente que no está encapsulado por el modelo en sí. En cambio, ese conocimiento se extiende alrededor de una pila de servicios relacionados. Esos servicios solo deben ser clientes del modelo, pero debido a su anemia, son responsables de ello. Por ejemplo, considere una Account
clase que no se puede usar para activar o desactivar cuentas o incluso buscar información sobre una cuenta a menos que se maneje a través de una AccountManager
clase. La cuenta debe ser responsable de las operaciones básicas en ella, no de una clase de administrador externo.
En la programación funcional, existe un problema similar cuando los tipos de datos no representan con precisión lo que se supone que deben modelar. Supongamos que necesitamos definir un tipo que represente ID de usuario. Una definición "anémica" indicaría que las ID de usuario son cadenas. Eso es técnicamente factible, pero se encuentra con grandes problemas porque las ID de usuario no se usan como cadenas arbitrarias. No tiene sentido concatenarlos o cortar subcadenas de ellos, Unicode realmente no debería importar, y deberían ser fácilmente incrustables en URL y otros contextos con limitaciones estrictas de caracteres y formatos.
La solución de este problema generalmente ocurre en algunas etapas. Un primer corte simple es decir "Bueno, a UserID
se representa de manera equivalente a una cadena, pero son de diferentes tipos y no se puede usar uno donde se espera el otro". Haskell (y algunos otros lenguajes funcionales escritos) proporciona esta característica a través de newtype
:
newtype UserID = UserID String
Esto define una UserID
función que cuando se le da un String
Construye un valor que se trató como un UserID
por el sistema de tipo, pero que todavía es sólo una String
en tiempo de ejecución. Ahora las funciones pueden declarar que requieren una en UserID
lugar de una cadena; usando UserID
s donde anteriormente estaba usando cadenas de protección contra el código que concatena dos UserID
s juntos. El sistema de tipos garantiza que no puede ocurrir, no se requieren pruebas.
La debilidad aquí es que el código todavía puede tomar cualquier me String
gusta arbitrario "hello"
y construir un a UserID
partir de él. Otros pasos incluyen la creación de una función de "constructor inteligente" que cuando se le da una cadena verifica algunos invariantes y solo devuelve un UserID
si están satisfechos. A continuación, el "tonto" UserID
constructor se hace de manera privada, si un cliente quiere una UserID
que debe utilizar el constructor inteligente, evitando así identificadores de usuario con formato incorrecto de venir a la existencia.
Incluso otros pasos definen el UserID
tipo de datos de tal manera que es imposible construir uno que esté mal formado o sea "incorrecto", simplemente por definición. Por ejemplo, definiendo a UserID
como una lista de dígitos:
data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]
Para construir una UserID
lista de dígitos se debe proporcionar. Dada esta definición, es trivial mostrar que es imposible UserID
que exista un elemento que no pueda representarse en una URL. La definición de modelos de datos como este en Haskell a menudo se ve favorecida por características avanzadas del sistema de tipos, como Tipos de datos y Tipos de datos algebraicos generalizados (GADT) , que permiten que el sistema de tipos defina y demuestre más invariantes sobre su código. Cuando los datos se desacoplan del comportamiento, su definición de datos es el único medio que tiene para imponer el comportamiento.