Cách mô tả vấn đề "mô hình thiếu máu" không được dịch tốt cho FP. Đầu tiên nó cần phải được khái quát phù hợp. Thực chất, một mô hình thiếu máu là một mô hình chứa đựng kiến thức về cách sử dụng đúng cách mà mô hình không được gói gọn trong chính mô hình đó. Thay vào đó, kiến thức đó được lan truyền xung quanh một đống dịch vụ liên quan. Những dịch vụ đó chỉ nên là khách hàng của mô hình, nhưng do thiếu máu nên họ phải chịu trách nhiệm về nó. Ví dụ: xem xét một Account
lớp không thể được sử dụng để kích hoạt hoặc hủy kích hoạt tài khoản hoặc thậm chí tra cứu thông tin về tài khoản trừ khi được xử lý thông qua một AccountManager
lớp. Tài khoản phải chịu trách nhiệm cho các hoạt động cơ bản trên nó, không phải một số lớp quản lý bên ngoài.
Trong lập trình chức năng, một vấn đề tương tự tồn tại khi các kiểu dữ liệu không thể hiện chính xác những gì chúng được cho là mô hình hóa. Giả sử chúng ta cần xác định một loại đại diện cho ID người dùng. Một định nghĩa "thiếu máu" sẽ nói rằng ID người dùng là các chuỗi. Điều đó khả thi về mặt kỹ thuật, nhưng gặp phải vấn đề lớn vì ID người dùng không được sử dụng như các chuỗi tùy ý. Thật vô nghĩa khi ghép chúng hoặc cắt bỏ các chuỗi con của chúng, Unicode không thực sự quan trọng và chúng có thể dễ dàng nhúng vào URL và các bối cảnh khác với các giới hạn về ký tự và định dạng nghiêm ngặt.
Giải quyết vấn đề này thường xảy ra trong một vài giai đoạn. Cách cắt đầu tiên đơn giản là nói "Chà, a UserID
được biểu diễn tương đương với một chuỗi, nhưng chúng là các loại khác nhau và bạn không thể sử dụng một trong những nơi bạn mong đợi khác." Haskell (và một số ngôn ngữ chức năng đã nhập khác) cung cấp tính năng này thông qua newtype
:
newtype UserID = UserID String
Điều này xác định một UserID
hàm mà khi được cung cấp sẽ String
xây dựng một giá trị được xử lý như một UserID
hệ thống loại, nhưng vẫn chỉ là một String
thời gian chạy. Bây giờ các hàm có thể khai báo rằng chúng yêu cầu UserID
thay vì một chuỗi; sử dụng UserID
s nơi mà trước đây bạn đang sử dụng các bộ bảo vệ chuỗi chống lại mã nối hai UserID
s với nhau. Hệ thống loại đảm bảo không thể xảy ra, không cần kiểm tra.
Điểm yếu ở đây là mã vẫn có thể lấy bất kỳ tùy ý String
nào "hello"
và xây dựng một UserID
từ nó. Các bước tiếp theo bao gồm tạo hàm "hàm tạo thông minh" mà khi được cung cấp một chuỗi sẽ kiểm tra một số bất biến và chỉ trả về UserID
nếu chúng hài lòng. Sau đó, hàm tạo "câm" UserID
được đặt ở chế độ riêng tư, vì vậy nếu khách hàng muốn, UserID
họ phải sử dụng hàm tạo thông minh, do đó ngăn các ID người dùng không đúng định dạng xuất hiện.
Thậm chí các bước tiếp theo xác định UserID
loại dữ liệu theo cách không thể xây dựng một kiểu không đúng hoặc "không đúng", chỉ đơn giản là theo định nghĩa. Chẳng hạn, định nghĩa UserID
một danh sách các chữ số:
data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]
Để xây dựng một UserID
danh sách các chữ số phải được cung cấp. Đưa ra định nghĩa này, thật tầm thường khi cho thấy rằng không thể UserID
tồn tại mà không thể được thể hiện trong một URL. Việc xác định các mô hình dữ liệu như thế này trong Haskell thường được hỗ trợ bởi các tính năng hệ thống loại nâng cao như Kiểu dữ liệu và Kiểu dữ liệu đại số tổng quát (GADTs) , cho phép hệ thống loại xác định và chứng minh nhiều bất biến hơn về mã của bạn. Khi dữ liệu được tách rời khỏi hành vi, định nghĩa dữ liệu của bạn là phương tiện duy nhất bạn phải thực thi hành vi.