GADT cung cấp cú pháp rõ ràng và tốt hơn để mã bằng cách sử dụng các loại Hiện sinh bằng cách cung cấp ẩn cho forall
Tôi nghĩ có thỏa thuận chung rằng cú pháp GADT là tốt hơn. Tôi không nói rằng đó là vì các GADT cung cấp các quảng cáo ngầm, mà là vì cú pháp ban đầu, được kích hoạt với ExistentialQuantification
tiện ích mở rộng, có khả năng gây nhầm lẫn / gây hiểu lầm. Cú pháp đó, tất nhiên, trông giống như:
data SomeType = forall a. SomeType a
hoặc với một ràng buộc:
data SomeShowableType = forall a. Show a => SomeShowableType a
và tôi nghĩ rằng sự đồng thuận là việc sử dụng từ khóa forall
ở đây cho phép loại dễ bị nhầm lẫn với loại hoàn toàn khác nhau:
data AnyType = AnyType (forall a. a) -- need RankNTypes extension
Một cú pháp tốt hơn có thể đã sử dụng một exists
từ khóa riêng , vì vậy bạn sẽ viết:
data SomeType = SomeType (exists a. a) -- not valid GHC syntax
Cú pháp GADT, dù được sử dụng với ẩn hoặc rõ ràng forall
, đồng đều hơn giữa các loại này và dường như dễ hiểu hơn. Ngay cả với một forall
định nghĩa rõ ràng , định nghĩa sau đây có ý tưởng rằng bạn có thể lấy một giá trị của bất kỳ loại nào a
và đặt nó trong một hình đơn sắc SomeType'
:
data SomeType' where
SomeType' :: forall a. (a -> SomeType') -- parentheses optional
và thật dễ dàng để thấy và hiểu sự khác biệt giữa loại đó và:
data AnyType' where
AnyType' :: (forall a. a) -> AnyType'
Các kiểu tồn tại dường như không quan tâm đến loại chúng chứa nhưng mẫu phù hợp với chúng nói rằng tồn tại một số loại chúng ta không biết nó thuộc loại nào cho đến khi & trừ khi chúng ta sử dụng Kiểu chữ hoặc Dữ liệu.
Chúng tôi sử dụng chúng khi chúng tôi muốn Ẩn các loại (ví dụ: đối với Danh sách không đồng nhất) hoặc chúng tôi không thực sự biết loại nào trong Thời gian biên dịch.
Tôi đoán những thứ này không quá xa, mặc dù bạn không phải sử dụng Typeable
hoặcData
sử dụng các loại tồn tại. Tôi nghĩ sẽ chính xác hơn khi nói một loại tồn tại cung cấp một "hộp" được đánh máy tốt xung quanh một loại không xác định. Hộp này "ẩn" loại theo nghĩa, cho phép bạn tạo một danh sách không đồng nhất của các hộp như vậy, bỏ qua các loại chúng chứa. Nó chỉ ra rằng một tồn tại không bị ràng buộc, như SomeType'
ở trên là khá vô dụng, nhưng là một loại bị hạn chế:
data SomeShowableType' where
SomeShowableType' :: forall a. (Show a) => a -> SomeShowableType'
cho phép bạn khớp mẫu để nhìn trộm bên trong "hộp" và cung cấp các tiện ích lớp loại:
showIt :: SomeShowableType' -> String
showIt (SomeShowableType' x) = show x
Lưu ý rằng điều này hoạt động cho bất kỳ loại lớp, không chỉ Typeable
hoặcData
.
Liên quan đến sự nhầm lẫn của bạn về trang 20 của trang trình bày, tác giả nói rằng không thể có chức năng nào tồn tại Worker
để yêu cầu Worker
có một Buffer
trường hợp cụ thể. Bạn có thể viết một hàm để tạo một kiểu Worker
sử dụng cụ thể Buffer
, nhưMemoryBuffer
:
class Buffer b where
output :: String -> b -> IO ()
data Worker x = forall b. Buffer b => Worker {buffer :: b, input :: x}
data MemoryBuffer = MemoryBuffer
instance Buffer MemoryBuffer
memoryWorker = Worker MemoryBuffer (1 :: Int)
memoryWorker :: Worker Int
nhưng nếu bạn viết một hàm lấy tham Worker
số làm đối số, thì nó chỉ có thể sử dụng các Buffer
phương tiện lớp loại chung (ví dụ: hàm output
):
doWork :: Worker Int -> IO ()
doWork (Worker b x) = output (show x) b
Không thể yêu cầu b
một loại bộ đệm cụ thể, thậm chí thông qua khớp mẫu:
doWorkBroken :: Worker Int -> IO ()
doWorkBroken (Worker b x) = case b of
MemoryBuffer -> error "try this" -- type error
_ -> error "try that"
Cuối cùng, thông tin thời gian chạy về các kiểu tồn tại được cung cấp thông qua các đối số "từ điển" ẩn cho các kiểu chữ có liên quan. CácWorker
loại trên, trong addtion để có lĩnh vực cho bộ đệm và đầu vào, cũng có một lĩnh vực tiềm ẩn vô hình mà điểm đến Buffer
từ điển (hơi giống như v-bảng, mặc dù nó hầu như không lớn, vì nó chỉ chứa một con trỏ để phù hợp output
chức năng).
Trong nội bộ, lớp loại Buffer
được biểu diễn dưới dạng kiểu dữ liệu với các trường chức năng và các thể hiện là "từ điển" của loại này:
data Buffer' b = Buffer' { output' :: String -> b -> IO () }
dBuffer_MemoryBuffer :: Buffer' MemoryBuffer
dBuffer_MemoryBuffer = Buffer' { output' = undefined }
Kiểu tồn tại có một trường ẩn cho từ điển này:
data Worker' x = forall b. Worker' { dBuffer :: Buffer' b, buffer' :: b, input' :: x }
và một chức năng như thế doWork
hoạt động trên Worker'
các giá trị hiện sinh được thực hiện như sau:
doWork' :: Worker' Int -> IO ()
doWork' (Worker' dBuf b x) = output' dBuf (show x) b
Đối với một lớp loại chỉ có một hàm, từ điển thực sự được tối ưu hóa cho một kiểu mới, vì vậy trong ví dụ này, Worker
kiểu tồn tại bao gồm một trường ẩn bao gồm một con trỏ hàm đến output
hàm cho bộ đệm và đó là thông tin thời gian chạy duy nhất cần thiết bởi doWork
.