Làm rõ về các loại tồn tại trong Haskell


10

Tôi đang cố gắng hiểu các loại Hiện sinh trong Haskell và tìm thấy một tệp PDF http://www.ii.uni.wroc.pl/~dabi/cifts/ZPF15/rlasocha/prezentacja.pdf

Xin vui lòng sửa những hiểu biết dưới đây của tôi mà tôi có cho đến bây giờ.

  • 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.
  • GADT's cung cấp cú pháp rõ ràng và tốt hơn để mã sử dụng các loại Hiện sinh bằng cách cung cấp tiềm ẩn forall' s

Nghi ngờ của tôi

  • Trong trang 20 của PDF ở trên, nó được đề cập cho đoạn mã bên dưới rằng Hàm không thể yêu cầu Bộ đệm cụ thể. Tại sao nó như vậy? Khi tôi soạn thảo một Hàm tôi biết chính xác loại bộ đệm nào tôi sẽ sử dụng mặc dù tôi có thể không biết tôi sẽ đưa dữ liệu nào vào đó. Có gì sai khi có :: Worker MemoryBuffer IntNếu họ thực sự muốn trừu tượng hóa Bộ đệm, họ có thể có loại Sum data Buffer = MemoryBuffer | NetBuffer | RandomBuffervà có loại như:: Worker Buffer Int
data Worker x = forall b. Buffer b => Worker {buffer :: b, input :: x}
data MemoryBuffer = MemoryBuffer

memoryWorker = Worker MemoryBuffer (1 :: Int)
memoryWorker :: Worker Int
  • Vì Haskell là ngôn ngữ Xóa Kiểu hoàn toàn như C, Làm thế nào để biết tại Runtime sẽ gọi chức năng nào. Đây có phải là thứ gì đó giống như chúng ta sẽ duy trì một vài thông tin và chuyển vào một Bảng chức năng V khổng lồ và trong thời gian chạy, nó sẽ tìm ra từ V-Table? Nếu nó là như vậy thì loại thông tin nào nó sẽ lưu trữ?

Câu trả lời:


8

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 ExistentialQuantificationtiệ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 existstừ 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 avà đặ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 Typeablehoặ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 Workercó một Buffertrường hợp cụ thể. Bạn có thể viết một hàm để tạo một kiểu Workersử 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 Workersố làm đối số, thì nó chỉ có thể sử dụng các Bufferphươ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 bmộ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 Buffertừ đ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 outputchứ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ế doWorkhoạ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, Workerkiểu tồn tại bao gồm một trường ẩn bao gồm một con trỏ hàm đến outputhàm cho bộ đệm và đó là thông tin thời gian chạy duy nhất cần thiết bởi doWork.


Có tồn tại giống như hạng 1 cho khai báo dữ liệu? Hiện tại là cách để xử lý các chức năng ảo trong Haskell như trong bất kỳ ngôn ngữ OOP nào?
Pawan Kumar

1
Tôi có lẽ không nên gọi AnyTypeloại hạng 2; Điều đó thật khó hiểu và tôi đã xóa nó. Hàm tạo AnyTypehoạt động như một hàm hạng 2 và hàm tạo SomeTypehoạt động một hàm hạng 1 (giống như hầu hết các kiểu không tồn tại), nhưng đó không phải là một đặc điểm rất hữu ích. Nếu bất cứ điều gì, điều làm cho các loại này thú vị là chúng xếp hạng 0 (nghĩa là không được định lượng qua một biến loại và rất đơn hình) ngay cả khi chúng "chứa" các loại được định lượng.
KA Buhr

1
Các lớp loại (và cụ thể là các hàm phương thức của chúng) chứ không phải là các kiểu tồn tại, có lẽ là Haskell trực tiếp nhất tương đương với các hàm ảo. Theo nghĩa kỹ thuật, các lớp và đối tượng của ngôn ngữ OOP có thể được xem là các loại và giá trị hiện sinh, nhưng thực tế, thường có nhiều cách tốt hơn để thực hiện kiểu đa hình "hàm ảo" trong Haskell so với các kiểu tồn tại, chẳng hạn như kiểu tổng, loại lớp, và / hoặc đa hình tham số.
KA Buhr

4

Trong trang 20 của PDF ở trên, nó được đề cập cho đoạn mã bên dưới rằng Hàm không thể yêu cầu Bộ đệm cụ thể. Tại sao nó như vậy?

Bởi vì Worker, như được định nghĩa, chỉ lấy một đối số, loại trường "đầu vào" (biến loại x). Vd Worker Intlà một loại. bThay vào đó, biến loại không phải là một tham số của Worker, nhưng là một loại "biến cục bộ", có thể nói như vậy. Nó không thể được thông qua như trongWorker Int String - điều đó sẽ gây ra lỗi loại.

Nếu chúng ta thay vào đó xác định:

data Worker x b = Worker {buffer :: b, input :: x}

sau đó Worker Int Stringsẽ hoạt động, nhưng loại không còn tồn tại - bây giờ chúng ta luôn phải vượt qua loại bộ đệm.

Vì Haskell là ngôn ngữ Xóa Kiểu hoàn toàn như C, Làm thế nào để biết tại Runtime sẽ gọi chức năng nào. Đây có phải là thứ gì đó giống như chúng ta sẽ duy trì một vài thông tin và chuyển vào một Bảng chức năng V khổng lồ và trong thời gian chạy, nó sẽ tìm ra từ V-Table? Nếu nó là như vậy thì loại thông tin nào nó sẽ lưu trữ?

Điều này gần đúng. Nói ngắn gọn, mỗi khi bạn áp dụng hàm tạo Worker, GHC sẽ bnhập loại từ các đối số Workervà sau đó tìm kiếm một thể hiệnBuffer b . Nếu điều đó được tìm thấy, GHC bao gồm một con trỏ bổ sung cho thể hiện trong đối tượng. Ở dạng đơn giản nhất, nó không quá khác biệt so với "con trỏ tới vtable" được thêm vào từng đối tượng trong OOP khi có các hàm ảo.

Trong trường hợp chung, nó có thể phức tạp hơn nhiều, mặc dù. Trình biên dịch có thể sử dụng một biểu diễn khác và thêm nhiều con trỏ thay vì một con trỏ (giả sử, trực tiếp thêm các con trỏ vào tất cả các phương thức cá thể), nếu điều đó tăng tốc mã. Ngoài ra, đôi khi trình biên dịch cần sử dụng nhiều phiên bản để đáp ứng một ràng buộc. Ví dụ: nếu chúng ta cần lưu trữ ví dụ cho Eq [Int]... thì không chỉ có một mà là hai: một cho Intvà một cho danh sách, và hai cần phải được kết hợp (trong thời gian chạy, chặn tối ưu hóa).

Thật khó để đoán chính xác GHC làm gì trong từng trường hợp: điều đó phụ thuộc vào hàng tấn tối ưu hóa có thể hoặc không thể kích hoạt.

Bạn có thể thử googling cho việc triển khai "dựa trên từ điển" của các lớp loại để xem thêm về những gì đang diễn ra. Bạn cũng có thể yêu cầu GHC in Core được tối ưu hóa bên trong -ddump-simplvà quan sát các từ điển đang được xây dựng, lưu trữ và chuyển xung quanh. Tôi phải cảnh báo bạn: Core khá thấp và có thể khó đọc lúc đầu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.