2013-07-25 32 views
16

Có thể tạo một kiểu chữ không thể thừa nhận thành viên mới nữa (có lẽ bằng cách sử dụng các biên mô-đun)? Tôi có thể từ chối xuất một hàm cần thiết cho một định nghĩa cá thể hoàn chỉnh, nhưng điều đó chỉ dẫn đến một lỗi thời gian chạy nếu ai đó tạo ra một cá thể không hợp lệ. Tôi có thể làm cho nó một lỗi thời gian biên dịch?Loại lớp học khép kín

+2

Những câu trả lời là tất cả tuyệt vời-cảm ơn bạn everone! –

Trả lời

13

Tôi tin câu trả lời là có đủ điều kiện, tùy thuộc vào những gì bạn đang cố gắng đạt được.

Bạn có thể không tự xuất tên lớp loại khỏi mô-đun giao diện , trong khi vẫn xuất tên của các hàm loại lớp. Sau đó, không ai có thể làm cho một thể hiện của lớp bởi vì không ai có thể đặt tên cho nó!

Ví dụ:

module Foo (
    foo, 
    bar 
) where 

class SecretClass a where 
    foo :: a 
    bar :: a -> a -> a 

instance SecretClass Int where 
    foo = 3 
    bar = (+) 

Nhược điểm là không ai có thể viết một kiểu với lớp học của bạn là một hạn chế trong hai. Điều này không hoàn toàn hoàn toàn ngăn người viết chức năng có loại như vậy, bởi vì trình biên dịch sẽ vẫn có thể suy ra loại. Nhưng nó sẽ rất khó chịu.

Bạn có thể giảm thiểu nhược điểm bằng cách cung cấp một lớp loại trống khác, với lớp "đã đóng" của bạn dưới dạng siêu lớp. Bạn làm cho mọi cá thể của lớp ban đầu của bạn cũng là một cá thể của lớp con, và bạn xuất lớp con (cùng với tất cả các hàm lớp kiểu), nhưng không phải là lớp siêu. (Để rõ ràng, bạn có lẽ nên sử dụng lớp "công khai" chứ không phải lớp "bí mật" trong tất cả các loại mà bạn vạch trần, nhưng tôi tin rằng nó hoạt động theo một trong hai cách).

Ví dụ:

{-# LANGUAGE FlexibleInstances, UndecidableInstances #-} 

module Foo ( 
    PublicClass, 
    foo, 
    bar 
) where 

class SecretClass a where 
    foo :: a 
    bar :: a -> a -> a 

class SecretClass a => PublicClass a 

instance SecretClass Int where 
    foo = 3 
    bar = (+) 

instance SecretClass a => PublicClass a 

Bạn có thể làm mà không có phần mở rộng nếu bạn sẵn sàng để tự tuyên bố một thể hiện của PublicClass cho mỗi trường hợp của SecretClass.

mã khách hàng Bây giờ có thể sử dụng PublicClass để viết những hạn chế kiểu lớp, nhưng mỗi thể hiện của PublicClass đòi hỏi một thể hiện của SecretClass cho cùng loại, và không có cách nào để khai báo một thể hiện mới của SecretClass không ai có thể thực hiện bất kỳ loại nhiều trường hợp của PublicClass .

Tất cả điều này không giúp bạn có được trình biên dịch xử lý lớp học là "đã đóng". Nó sẽ vẫn phàn nàn về các biến kiểu mơ hồ có thể được giải quyết bằng cách chọn trường hợp duy nhất có thể nhìn thấy của "đóng".


ý kiến ​​tinh khiết: nó thường là một ý tưởng tốt để có một mô-đun nội bộ riêng biệt với một tên đáng sợ trong đó xuất khẩu tất cả mọi thứ để bạn có thể nhận được vào nó để thử nghiệm/gỡ lỗi, với một mô-đun giao diện đó nhập khẩu các mô-đun nội bộ và chỉ xuất những thứ bạn muốn xuất.

Tôi đoán với các tiện ích mở rộng ai đó có thể khai báo một trường hợp trùng lặp mới. Ví dụ. nếu bạn đã cung cấp một phiên bản cho [a], ai đó có thể tuyên bố một phiên bản mới của PublicClass cho [Int], có thể được xem là phiên bản SecretClass cho [a].Nhưng cho rằng PublicClass không có chức năng và họ không thể viết một thể hiện của SecretClass Tôi không thể thấy rằng nhiều có thể được thực hiện với điều đó.

+0

Bất cứ ai cũng có thể tạo ra các trường hợp của 'PublicClass' mặc dù, và trong khi tôi không biết tel nào đang cố gắng đạt được, tôi không thể nghĩ ngay đến một tình huống mà điều này sẽ không có tác động tương tự như xuất' SecretClass' trong địa điểm đầu tiên. Bạn có thể? – firefrorefiddle

+1

@MikeHartl Không, bạn không thể tạo các phiên bản 'PublicClass' cho các kiểu không phải là trường hợp của' SecretClass'. Bạn nhận được một lỗi như 'Không có trường hợp cho (Foo.SecretClass Bool) phát sinh từ các siêu lớp của một khai báo thể hiện Có thể sửa chữa: thêm một khai báo cá thể cho (Foo.SecretClass Bool)'. Và bạn không thể thêm trường hợp đó, bởi vì 'SecretClass' không nằm trong phạm vi bất cứ nơi nào bên ngoài module. – Ben

+0

Ah! Lỗi của tôi. Tôi nhầm nó với 'instance (SecretClass a) => PublicClass a'. – firefrorefiddle

18

Kể từ GHC 7.8.1, closed type families có thể được khai báo, và tôi nghĩ rằng với sự giúp đỡ của họ, và ConstraintKinds, bạn có thể làm điều này:

type family SecretClass (a :: *) :: Constraint where 
    SecretClass Int =() 

SecretClass a tạo thành một hạn chế, tương đương với một lớp loại, và vì gia đình không thể được mở rộng bởi bất kỳ ai, không thể xác định được các trường hợp khác của "lớp".

(Điều này thực sự chỉ là suy đoán, vì tôi không thể kiểm tra nó, nhưng các mã trong this interesting link làm cho nó trông giống như nó sẽ làm việc.)

+1

Ý tưởng gọn gàng, cảm ơn. –

+1

Cả hai liên kết đến bài của Patrick Bahr đều là 404. Hãy thử [liên kết này] (http://bahr.io/pubs/files/serrano15haskell-paper.pdf), từ trang của Bahr. –

7

Bạn có thể cấu trúc lại các typeclass vào một tuyên bố dữ liệu (cú pháp kỷ lục sử dụng) chứa tất cả các chức năng mà typeclass của bạn có. Một danh sách các trường hợp hữu hạn cố định có vẻ như bạn không cần lớp học.

Điều này tất nhiên về cơ bản những gì trình biên dịch đang hoạt động bình thường với cảnh của bạn.

Điều này sẽ cho phép bạn xuất danh sách các trường hợp dưới dạng hàm thành kiểu dữ liệu của bạn và bạn có thể xuất chúng nhưng không phải là hàm tạo cho kiểu dữ liệu. Tương tự như vậy, bạn có thể hạn chế xuất các chức năng truy cập và chỉ xuất giao diện mà bạn thực sự muốn.

Tính năng này hoạt động tốt vì các loại dữ liệu không tuân theo giả định thế giới mở mô-đun-qua biên giới các kiểu chữ.

Đôi khi việc thêm độ phức tạp của hệ thống chỉ làm cho mọi thứ trở nên khó khăn hơn.

11

Bạn có thể mã hóa các loại lớp đã đóng qua các họ loại đã đóng, về cơ bản, chúng có thể được mã hóa thành các họ loại liên kết. Chìa khóa cho giải pháp này là các cá thể của một họ kiểu có liên quan có bên trong một cá thể kiểu lớp, và chỉ có thể có một cá thể kiểu loại cho mỗi kiểu đơn lẻ.

Lưu ý rằng phương pháp này độc lập với hệ thống mô-đun. Thay vì dựa vào ranh giới mô-đun, chúng tôi cung cấp một danh sách rõ ràng về những trường hợp nào là hợp pháp. Điều này có nghĩa, một mặt, rằng các cá thể hợp pháp có thể được trải rộng trên nhiều mô-đun hoặc thậm chí các gói và mặt khác, chúng tôi không thể cung cấp các trường hợp bất hợp pháp ngay cả trong cùng một mô-đun.

Đối với câu trả lời này, tôi cho rằng chúng tôi muốn đóng lớp sau để nó chỉ có thể được khởi tạo cho các loại IntInteger, nhưng không phải với nhiều loại khác:

-- not yet closed 
class Example a where 
    method :: a -> a 

Đầu tiên, chúng ta cần một ít khuôn khổ cho việc mã hóa các loại gia đình đóng cửa như các loại gia đình liên quan.

{-# LANGUAGE TypeFamilies, EmptyDataDecls #-} 

class Closed c where 
    type Instance c a 

Tham số c đứng cho tên của gia đình loại và các thông số a là chỉ số của gia đình loại. Ví dụ gia đình của c cho a được mã hóa là Instance c a. Vì c cũng là tham số lớp, tất cả các phiên bản gia đình của c phải được cung cấp cùng nhau, trong một khai báo cá thể lớp đơn.

Bây giờ, chúng tôi sử dụng khung này để xác định họ loại đã đóng MemberOfExample để mã hóa IntIntegerOk và tất cả các loại khác thì không.

data MemberOfExample 
data Ok 

instance Closed MemberOfExample where 
    type Instance MemberOfExample Int = Ok 
    type Instance MemberOfExample Integer = Ok 

Cuối cùng, chúng tôi sử dụng họ loại khép kín này trong giới hạn siêu lớp Example.

class Instance MemberOfExample a ~ Ok => Example a where 
    method :: a -> a 

Chúng tôi có thể xác định các trường hợp hợp lệ cho IntInteger như bình thường.

instance Example Int where 
    method x = x + 1 

instance Example Integer where 
    method x = x + 1 

Nhưng chúng tôi không thể xác định các trường hợp không hợp lệ cho các loại khác hơn IntInteger.

-- GHC error: Couldn't match type `Instance MemberOfExample Float' with `Ok' 
instance Example Float where 
    method x = x + 1 

Và chúng tôi cũng không thể mở rộng tập hợp các loại hợp lệ.

-- GHC error: Duplicate instance declarations 
instance Closed MemberOfExample where 
    type Instance MemberOfExample Float = Ok 

-- GHC error: Associated type `Instance' must be inside a class instance 
type instance Instance MemberOfExample Float = Ok 

Thật không may, chúng tôi có thể viết các trường hợp giả mạo sau:

-- Unfortunately accepted 
instance Instance MemberOfExample Float ~ Ok => Example Float where 
    method x = x + 1 

Nhưng vì chúng ta sẽ không bao giờ có thể xả chế bình đẳng, tôi không nghĩ rằng chúng ta bao giờ có thể sử dụng nó cho bất cứ điều gì. Ví dụ, sau đây là từ chối:

-- Couldn't match type `Instance MemberOfExample Float' with `Ok' 
test = method (pi :: Float) 
+0

Điều này dường như rất giống với câu trả lời của @ Ben. Bạn có thể giải thích chúng khác nhau như thế nào không? –

+0

@tel: Sự khác biệt quan trọng nhất có vẻ là cách tiếp cận này độc lập với hệ thống mô-đun. Tôi đã thêm một đoạn vào câu trả lời để chỉ ra điều này. – Toxaris

1

Khi tất cả các bạn đang quan tâm rằng bạn có một bộ enumertated các trường hợp, sau đó lừa này có thể giúp:

class (Elem t '[Int, Integer, Bool] ~ True) => Closed t where 

type family Elem (t :: k) (ts :: [k]) :: Bool where 
    Elem a '[] = False 
    Elem a (a ': as) = True 
    Elem a (b ': bs) = Elem a bs 

instance Closed Int 
instance Closed Integer 
-- instance Closed Float -- ERROR 
Các vấn đề liên quan