Các kiểu hiện sinh không thực sự được coi là thực hành xấu trong lập trình chức năng. Tôi nghĩ điều khiến bạn vấp ngã là một trong những cách sử dụng phổ biến nhất cho hiện sinh là các phản mẫu kiểu chữ hiện sinh , điều mà nhiều người tin là thực hành xấu.
Mẫu này thường được tìm ra như một câu trả lời cho câu hỏi làm thế nào để có một danh sách các phần tử được gõ không đồng nhất mà tất cả đều thực hiện cùng một kiểu chữ. Ví dụ: bạn có thể muốn có một danh sách các giá trị có Show
thể hiện:
{-# LANGUAGE ExistentialTypes #-}
class Shape s where
area :: s -> Double
newtype Circle = Circle { radius :: Double }
instance Shape Circle where
area (Circle r) = pi * r^2
newtype Square = Square { side :: Double }
area (Square s) = s^2
data AnyShape = forall x. Shape x => AnyShape x
instance Shape AnyShape where
area (AnyShape x) = area x
example :: [AnyShape]
example = [AnyShape (Circle 1.0), AnyShape (Square 1.0)]
Vấn đề với mã như thế này là:
- Hoạt động hữu ích duy nhất bạn có thể thực hiện trên một
AnyShape
là lấy khu vực của nó.
- Bạn vẫn cần sử dụng hàm
AnyShape
tạo để đưa một trong các kiểu hình vào AnyShape
kiểu.
Vì vậy, hóa ra, đoạn mã đó không thực sự mang lại cho bạn bất cứ thứ gì mà đoạn mã ngắn hơn này không:
class Shape s where
area :: s -> Double
newtype Circle = Circle { radius :: Double }
instance Shape Circle where
area (Circle r) = pi * r^2
newtype Square = Square { side :: Double }
area (Square s) = s^2
example :: [Double]
example = [area (Circle 1.0), area (Square 1.0)]
Trong trường hợp các lớp đa phương thức, hiệu ứng tương tự thường có thể được thực hiện đơn giản hơn bằng cách sử dụng "bản ghi phương thức" mã hóa thay vì sử dụng một kiểu chữ như Shape
, bạn xác định một loại bản ghi có các trường là "phương thức" của Shape
loại và bạn viết các hàm để chuyển đổi các vòng tròn và hình vuông của bạn thành Shape
s.
Nhưng điều đó không có nghĩa là các loại tồn tại là một vấn đề! Ví dụ, trong Rust, chúng có một tính năng gọi là các đối tượng đặc điểm mà mọi người thường mô tả là một kiểu tồn tại trên một đặc điểm (phiên bản của các lớp loại của Rust). Nếu kiểu chữ hiện sinh là một phản mẫu trong Haskell, điều đó có nghĩa là Rust chọn một giải pháp tồi? Không! Động lực trong thế giới Haskell là về cú pháp và sự thuận tiện, không thực sự về nguyên tắc.
Một cách toán học hơn để đưa ra điều này là chỉ ra rằng AnyShape
loại từ phía trên và Double
có cấu trúc đẳng cấu là một "chuyển đổi không mất mát" giữa chúng (tốt, tiết kiệm cho độ chính xác của dấu phẩy động):
forward :: AnyShape -> Double
forward = area
backward :: Double -> AnyShape
backward x = AnyShape (Square (sqrt x))
Nói một cách nghiêm túc, bạn không được hoặc mất bất kỳ quyền lực nào bằng cách chọn cái này so với cái kia. Điều đó có nghĩa là sự lựa chọn nên dựa trên các yếu tố khác như dễ sử dụng hoặc hiệu suất.
Và hãy nhớ rằng các kiểu tồn tại có những cách sử dụng khác bên ngoài ví dụ về danh sách không đồng nhất này, vì vậy thật tốt khi có chúng. Ví dụ, ST
loại của Haskell , cho phép chúng ta viết các hàm hoàn toàn bên ngoài nhưng sử dụng bên trong các hoạt động đột biến bộ nhớ, sử dụng một kỹ thuật dựa trên các loại tồn tại để đảm bảo an toàn tại thời gian biên dịch.
Vì vậy, câu trả lời chung là không có câu trả lời chung chung. Việc sử dụng các kiểu tồn tại chỉ có thể được đánh giá trong ngữ cảnh và các câu trả lời có thể khác nhau tùy thuộc vào các tính năng và cú pháp được cung cấp bởi các ngôn ngữ khác nhau.