2015-05-14 15 views
8

Tôi sẽ bắt đầu bằng cách giới thiệu một vấn đề cụ thể (StackOverflow guys như thế). Giả sử bạn định nghĩa một kiểu đơn giảnTại sao các trường hợp chỉ phù hợp với đầu của họ?

data T a = T a 

kiểu Đây là một Functor, ApplicativeMonad. Bỏ qua việc tạo tự động, để có được những trường hợp đó, bạn phải viết từng trường hợp, mặc dù Monad ngụ ý Applicative, ngụ ý Functor. Hơn thế nữa, tôi có thể xác định một lớp học như thế này

class Wrapper f where 
    wrap :: a -> f a 
    unwrap :: f a -> a 

Đây là một tình trạng khá mạnh và nó chắc chắn ám chỉ Monad, nhưng tôi không thể viết

instance Wrapper f => Monad f where 
    return = wrap 
    fa >>= f = f $ unwrap fa 

vì này, vì một lý do , có nghĩa là "tất cả mọi thứ là một Monad (mỗi f), chỉ khi đó là một Wrapper", thay vì "tất cả mọi thứ đó là một Wrapper là một Monad".

Tương tự, bạn không thể xác định các trường hợp Monad a => Applicative aApplicative a => Functor a.

Một thứ khác mà bạn không thể làm (chỉ có thể liên quan, tôi thực sự không biết) là có một lớp là một siêu lớp của một lớp khác và cung cấp triển khai mặc định của lớp con. Chắc chắn, nó tuyệt vời mà class Applicative a => Monad a, nhưng nó ít hơn nhiều tuyệt vời mà tôi vẫn phải xác định các trường hợp Applicative trước khi tôi có thể xác định Monad một.

Đây không phải là một rant. Tôi đã viết rất nhiều vì nếu không điều này sẽ nhanh chóng được đánh dấu là "quá rộng" hoặc "không rõ ràng". Câu hỏi đặt ra tiêu đề. Tôi biết (ít nhất là tôi khá chắc chắn) rằng có một số lý do lý thuyết cho điều này, vì vậy tôi tự hỏi những gì chính xác là những lợi ích ở đây.

Như một câu hỏi phụ, tôi muốn hỏi nếu có giải pháp thay thế khả thi mà vẫn giữ tất cả (hoặc hầu hết) những lợi thế, nhưng cho phép những gì tôi đã viết.

Bổ sung: Tôi nghi ngờ một trong những câu trả lời có thể là thứ gì đó dọc theo dòng "Nếu loại của tôi là Wrapper, nhưng tôi không muốn sử dụng dụ Monad ngụ ý?". Điều này tôi hỏi, tại sao không thể trình biên dịch chỉ cần chọn một trong những cụ thể nhất? Nếu có instance Monad MyType, chắc chắn nó cụ thể hơn instance Wrapper a => Monad a.

+1

'unwrap' mô tả hành vi mà không có phương thức nào của' Monad' thực hiện; nếu không phải như vậy, 'unwrap' sẽ cho phép bạn trích xuất một' a' từ một tính toán 'IO a' (như' unsafePerformIO' làm), điều này sẽ đánh bại mục đích của việc có một đơn vị 'IO' trong lần đầu tiên địa điểm. Một lớp chỉ có thể ít mạnh hơn/biểu cảm hơn các lớp con của nó. – Jubobs

+1

Điều gì sẽ là định nghĩa của 'unwrap' cho' Nothing' khi định nghĩa cá thể cho kiểu 'Maybe'? – Sibi

+2

@Sibi Đơn giản, không có trường hợp nào cho Có thể. Tôi không nghĩ anh ta đã tuyên bố. Ngoài ra, tôi nghi ngờ Wrapper ngụ ý Monad mà không có bất kỳ luật nào (ngoài những luật tự do) trên đó. – Cubic

Trả lời

12

Có rất nhiều câu hỏi được cuộn vào một câu hỏi tại đây. Nhưng chúng ta hãy lấy chúng từng cái một.

Đầu tiên: tại sao không trình biên dịch nhìn vào bối cảnh dụ khi lựa chọn mà dụ sử dụng không? Điều này là để giữ cho tìm kiếm dụ hiệu quả. Nếu bạn yêu cầu trình biên dịch chỉ xem xét các cá thể mà các cá thể của nó được thỏa mãn, về cơ bản bạn yêu cầu trình biên dịch của bạn thực hiện tìm kiếm theo dõi ngược trong tất cả các trường hợp có thể, tại thời điểm đó bạn đã thực hiện 90% Prolog. Nếu, mặt khác, bạn lấy lập trường (như Haskell làm) mà bạn chỉ nhìn vào các thể hiện cá thể khi chọn cá thể nào để sử dụng, và sau đó chỉ cần thực thi ngữ cảnh cá thể, không có backtracking: tại mọi thời điểm, chỉ có một sự lựa chọn bạn có thể thực hiện.

Tiếp theo: tại sao không thể bạn đã một lớp là một lớp cha của nhau, và cung cấp một thực hiện mặc định của lớp con? Không có lý do cơ bản nào cho sự hạn chế này, do đó GHC cung cấp tính năng này như một phần mở rộng.Bạn có thể viết một cái gì đó như thế này:

{-# LANGUAGE DefaultSignatures #-} 
class Applicative f where 
    pure :: a -> f a 
    (<*>) :: f (a -> b) -> f a -> f b 

    default pure :: Monad f => a -> f a 
    default (<*>) :: Monad f => f (a -> b) -> f a -> f b 
    pure = return 
    (<*>) = ap 

Sau đó, một khi bạn đã cung cấp một instance Monad M where ..., bạn chỉ có thể viết instance Applicative M không có where khoản và có nó chỉ làm việc. Tôi không thực sự biết tại sao điều này không được thực hiện trong thư viện chuẩn.

Cuối cùng: tại sao trình biên dịch không thể cho phép nhiều phiên bản và chỉ cần chọn một phiên bản cụ thể nhất? Câu trả lời cho câu trả lời này là một sự kết hợp của hai phần trước: có những lý do cơ bản rất tốt, điều này không hiệu quả, nhưng GHC vẫn cung cấp một phần mở rộng thực hiện nó. Lý do cơ bản này không hoạt động tốt là trường hợp cụ thể nhất cho một giá trị đã cho không thể được biết trước khi chạy. Câu trả lời của GHC cho điều này là, đối với các giá trị đa hình, để chọn một giá trị cụ thể nhất tương thích với tính đa hình đầy đủ có sẵn. Nếu sau đó điều đó được monomorphised, tốt, quá xấu cho bạn. Kết quả của việc này là một số hàm có thể hoạt động trên một số dữ liệu với một cá thể và những hàm khác có thể hoạt động trên cùng một dữ liệu đó với một cá thể khác; điều này có thể dẫn đến lỗi rất tinh tế. Nếu sau tất cả các cuộc thảo luận này bạn vẫn nghĩ đó là một ý tưởng tốt, và từ chối học hỏi từ những sai lầm của người khác, bạn có thể bật IncoherentInstances.

Tôi nghĩ rằng bao gồm tất cả các câu hỏi.

+0

Cảm ơn bạn đã trả lời. Mọi thứ đều có ý nghĩa. Có thể đáng lưu ý là tính năng của GHC 7.10, 'OverlappingInstances' và' IncoherentInstances' không được sử dụng để ủng hộ các bản sao pragma cho từng cá thể https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/type -class-extensions.html # instance-overlap –

+0

Xin chào, tôi vừa mới nhận ra. Để viết chữ ký mặc định, 'Applicative' cần biết về' Monad', nhưng vì 'Applicative' là một superclass của' Monad', 'Monad' cần biết về' Applicative'. Điều này không có nghĩa là bạn chỉ có thể sử dụng 'DefaultSignatures' nếu tất cả các typeclasses của bạn nằm trong cùng một tệp? –

+0

@LukaHorvat Báo cáo Haskell cho phép nhập khẩu đệ quy một cách rõ ràng. (Tuy nhiên, triển khai thường đòi hỏi bạn phải nhảy qua một số vòng bổ sung nếu bạn cố gắng sử dụng tính năng này, ví dụ: GHC yêu cầu tệp .hs-boot để ngắt bất kỳ chu kỳ nhập nào.) –

4

Tính thống nhất và biên dịch riêng biệt.

Nếu chúng ta có hai trường hợp mà người đứng đầu cả hai trận đấu, nhưng có những hạn chế khác nhau, nói:

-- File: Foo.hs 

instance Monad m => Applicative m 
instance   Applicative Foo 

Sau đó một trong hai này là mã có giá trị sản xuất một thể hiện Applicative cho Foo, hoặc nó là một lỗi sản xuất hai Applicative trường hợp khác nhau cho Foo. Số nào là phụ thuộc vào việc một cá thể đơn lẻ có tồn tại cho Foo hay không. Đó là một vấn đề, bởi vì rất khó để đảm bảo rằng kiến ​​thức về việc liệu giữ Monad Foo sẽ làm cho nó vào trình biên dịch khi nó biên dịch mô-đun này.

Mô-đun khác (giả sử Bar.hs) có thể tạo ra một phiên bản Monad cho Foo. Nếu Foo.hs không nhập mô-đun đó (thậm chí gián tiếp), thì trình biên dịch sẽ biết như thế nào? Tồi tệ hơn, chúng tôi có thể thay đổi cho dù đây là lỗi hay định nghĩa hợp lệ bằng cách thay đổi liệu sau này chúng tôi có bao gồm Bar.hs trong chương trình cuối cùng hay không! Để làm việc này, chúng ta cần phải biết rằng tất cả các trường hợp tồn tại trong chương trình biên dịch cuối cùng được hiển thị trong mỗi mô-đun, dẫn đến kết luận rằng mỗi mô-đun là phụ thuộc của mọi mô-đun khác bất kể mô-đun thực sự nhập khác. Bạn sẽ phải đi khá xa dọc theo con đường để yêu cầu toàn bộ chương trình phân tích để hỗ trợ một hệ thống như vậy, mà làm cho việc phân phối các thư viện biên dịch trước khó có thể thực hiện được.

Cách duy nhất để tránh điều này là không bao giờ có GHC đưa ra quyết định dựa trên thông tin tiêu cực. Bạn không thể chọn một phiên bản dựa trên không-sự tồn tại của một phiên bản khác.

Điều này có nghĩa là các ràng buộc trên một thể hiện phải được bỏ qua đối với độ phân giải mẫu.Bạn cần phải chọn một thể hiện bất kể các ràng buộc có giữ lại hay không; nếu nó để lại nhiều hơn một cá thể có thể áp dụng, thì bạn sẽ cần thông tin tiêu cực (cụ thể là tất cả nhưng một trong số chúng yêu cầu những ràng buộc không giữ) để chấp nhận mã là hợp lệ.

Nếu bạn chỉ có một thể hiện thậm chí là một ứng cử viên và bạn không thể thấy bằng chứng về các ràng buộc của nó, bạn có thể chấp nhận mã bằng cách chuyển các ràng buộc vào nơi cá thể được sử dụng (chúng ta có thể dựa vào việc thông tin này cho các mô-đun khác, vì họ sẽ phải nhập mô-đun này, ngay cả khi chỉ gián tiếp); nếu những vị trí đó cũng không thể thấy một trường hợp bắt buộc, thì chúng sẽ tạo ra một lỗi thích hợp về một ràng buộc không hài lòng. Vì vậy, bằng cách bỏ qua các ràng buộc, chúng tôi đảm bảo rằng trình biên dịch có thể đưa ra quyết định đúng về các trường hợp ngay cả khi chỉ biết về các mô-đun khác mà nó nhập (quá cảnh); nó không phải biết về mọi thứ được xác định trong mọi mô-đun khác để biết ràng buộc nào không giữ.

+0

Cảm ơn bạn đã trả lời. –

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