Die Art und Weise, wie das Problem des "anämischen Modells" beschrieben wird, lässt sich nicht so gut auf FP übertragen, wie es ist. Zunächst muss es entsprechend verallgemeinert werden. Im Kern ist ein anämisches Modell ein Modell, das Wissen über die ordnungsgemäße Verwendung enthält, das nicht vom Modell selbst eingekapselt wird. Stattdessen wird dieses Wissen auf einen Stapel verwandter Dienste verteilt. Diese Dienste sollten nur Kunden des Modells sein, aber aufgrund ihrer Anämie werden sie dafür verantwortlich gemacht. Angenommen, eine Account
Klasse kann nicht zum Aktivieren oder Deaktivieren von Konten oder sogar zum Nachschlagen von Informationen zu einem Konto verwendet werden, es sei denn, sie wird über eine AccountManager
Klasse verarbeitet. Das Konto sollte für grundlegende Vorgänge verantwortlich sein, nicht für eine externe Manager-Klasse.
Bei der funktionalen Programmierung besteht ein ähnliches Problem, wenn Datentypen nicht genau das darstellen, was sie modellieren sollen. Angenommen, wir müssen einen Typ definieren, der Benutzer-IDs darstellt. Eine "anämische" Definition würde angeben, dass Benutzer-IDs Zeichenfolgen sind. Das ist technisch machbar, stößt aber auf große Probleme, da Benutzer-IDs nicht wie willkürliche Zeichenfolgen verwendet werden. Es macht keinen Sinn, sie zu verketten oder Teilzeichenfolgen daraus herauszuschneiden. Unicode sollte eigentlich keine Rolle spielen und sie sollten leicht in URLs und andere Kontexte mit strengen Zeichen- und Formatbeschränkungen eingebettet werden können.
Die Lösung dieses Problems erfolgt normalerweise in wenigen Schritten. Ein einfacher erster Schnitt lautet: "Nun, a UserID
wird gleichbedeutend mit einer Zeichenfolge dargestellt, aber es handelt sich um verschiedene Typen, und Sie können keinen verwenden, bei dem Sie den anderen erwarten." Haskell (und einige andere typisierte funktionale Sprachen) bietet diese Funktion über newtype
:
newtype UserID = UserID String
Dies definiert eine UserID
Funktion, die bei Angabe eines String
Konstrukts einen Wert erzeugt, der vom Typsystem wie ein behandeltUserID
wird, der aber nur String
zur Laufzeit vorliegt. Jetzt können Funktionen deklarieren, dass sie einen UserID
anstelle eines Strings benötigen . Verwenden von UserID
s, bei denen Sie zuvor Zeichenfolgen verwendet haben, um zu verhindern, dass Code zwei UserID
s zusammenfügt. Das Typensystem garantiert, dass dies nicht passieren kann, es sind keine Tests erforderlich.
Die Schwäche dabei ist, dass Code immer noch ein beliebiges String
Like nehmen "hello"
und daraus ein konstruieren UserID
kann. Weitere Schritte umfassen das Erstellen einer "Smart-Konstruktor" -Funktion, die bei Angabe eines Strings einige Invarianten überprüft und nur dann ein zurückgibt, UserID
wenn sie zufrieden sind. Dann wird der "dumme" UserID
Konstruktor privat gemacht. Wenn ein Client dies wünscht UserID
, muss er den intelligenten Konstruktor verwenden, wodurch verhindert wird, dass fehlerhafte Benutzer-IDs entstehen.
In weiteren Schritten wird der UserID
Datentyp so definiert , dass es nicht möglich ist , einen fehlerhaften oder "unpassenden" Datentyp zu erstellen . So definieren Sie beispielsweise eine UserID
als Liste von Ziffern:
data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]
Zum Erstellen einer UserID
Liste müssen Ziffern angegeben werden. In Anbetracht dieser Definition ist es trivial zu zeigen, dass es unmöglich ist UserID
, dass ein Element existiert, das nicht in einer URL dargestellt werden kann. Das Definieren solcher Datenmodelle in Haskell wird häufig durch erweiterte Typsystemfunktionen wie Datentypen und Generalized Algebraic Data Types (GADTs) unterstützt , mit denen das Typsystem mehr Invarianten für Ihren Code definieren und nachweisen kann. Wenn Daten vom Verhalten entkoppelt sind, müssen Sie das Verhalten nur durch Ihre Datendefinition erzwingen.