2012-07-19 29 views
31

Tôi đã đọc qua thông báo của ClassyPrelude và đã đến đây:Haskell: Bình đẳng hạn chế trong trường hợp

instance (b ~ c, CanFilterFunc b a) => CanFilter (b -> c) a where 
    filter = filterFunc 

Nhà văn sau đó nói rằng điều này sẽ không làm việc:

instance (CanFilterFunc b a) => CanFilter (c -> c) a where 
    filter = filterFunc 

Mà làm cho tinh thần để tôi, như c hoàn toàn không liên quan đến ràng buộc ở bên trái.

Tuy nhiên, những gì không được đề cập trong bài viết và những gì tôi không hiểu được tại sao điều này sẽ không làm việc:

instance (CanFilterFunc b a) => CanFilter (b -> b) a where 
    filter = filterFunc 

một ai đó có thể giải thích tại sao điều này là khác nhau để định nghĩa nêu đầu tiên? Có lẽ một ví dụ làm việc về suy luận kiểu GHC sẽ hữu ích?

Trả lời

49

Michael đã đưa ra một lời giải thích tốt trong bài viết trên blog của mình, nhưng tôi sẽ cố gắng minh họa nó bằng một ví dụ (contrived, nhưng tương đối nhỏ).

Chúng ta cần mở rộng sau:

{-# LANGUAGE FlexibleInstances, TypeFamilies #-} 

Hãy xác định một lớp học đó là đơn giản hơn CanFilter, chỉ với một tham số. Tôi đang xác định hai bản sao của lớp học, bởi vì tôi muốn chứng minh sự khác biệt về hành vi giữa hai trường hợp:

class Twice1 f where 
    twice1 :: f -> f 

class Twice2 f where 
    twice2 :: f -> f 

Bây giờ, hãy xác định một cá thể cho mỗi lớp. Đối với Twice1, chúng tôi sửa các biến loại giống nhau trực tiếp và đối với Twice2, chúng tôi cho phép chúng khác nhau, nhưng thêm ràng buộc bình đẳng.

instance Twice1 (a -> a) where 
    twice1 f = f . f 

instance (a ~ b) => Twice2 (a -> b) where 
    twice2 f = f . f 

Để thấy sự khác biệt, chúng ta hãy xác định một chức năng quá tải như thế này:

class Example a where 
    transform :: Int -> a 

instance Example Int where 
    transform n = n + 1 

instance Example Char where 
    transform _ = 'x' 

Bây giờ chúng ta đang ở một điểm mà chúng ta có thể thấy sự khác biệt. Một khi chúng ta định nghĩa

apply1 x = twice1 transform x 
apply2 x = twice2 transform x 

và yêu cầu GHC cho các loại suy ra, chúng tôi nhận được rằng

apply1 :: (Example a, Twice1 (Int -> a)) => Int -> a 
apply2 :: Int -> Int 

Tại sao vậy? Vâng, trường hợp cho Twice1 chỉ kích hoạt khi nguồn và loại mục tiêu của hàm giống nhau. Đối với transform và bối cảnh đã cho, chúng tôi không biết điều đó. GHC sẽ chỉ áp dụng một trường hợp một khi phía bên tay phải phù hợp, vì vậy chúng tôi được trái với bối cảnh chưa được giải quyết. Nếu chúng tôi cố gắng nói apply1 0, sẽ có lỗi loại cho biết rằng vẫn không có đủ thông tin để giải quyết quá tải. Chúng tôi phải chỉ định rõ ràng loại kết quả là Int trong trường hợp này để vượt qua.

Tuy nhiên, trong Twice2, phiên bản dành cho bất kỳ loại chức năng nào. GHC sẽ ngay lập tức giải quyết nó (GHC không bao giờ backtracks, vì vậy nếu một instance rõ ràng khớp, nó luôn luôn được chọn), và sau đó cố gắng thiết lập các điều kiện tiên quyết: trong trường hợp này, ràng buộc bình đẳng, sau đó buộc loại kết quả là Int và cho phép chúng ta để giải quyết ràng buộc Example. Chúng tôi có thể nói apply2 0 mà không cần chú thích thêm.

Vì vậy, đây là một điểm khá tinh tế về giải pháp thể hiện của GHC và ràng buộc bình đẳng ở đây giúp kiểm tra loại GHC theo cách yêu cầu người dùng ít chú thích hơn.

+0

Cảm ơn. Tôi nghi ngờ nó như thế, nhưng ví dụ đơn giản của bạn làm mọi việc rõ ràng hơn nhiều. – Clinton

Các vấn đề liên quan