2012-07-29 26 views
9

xem xét ví dụ đoạn mã sau:Haskell sub-typeclass yêu cầu UndecidableInstances?

{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE UndecidableInstances #-} -- Is there a way to avoid this? 

-- A generic class with a generic function. 
class Foo a where 
    foo :: a -> a 

-- A specific class with specific functions. 
class Bar a where 
    bar :: a -> a 
    baz :: a -> a 

-- Given the specific class functions, we can implement the generic class function. 
instance Bar a => Foo a where 
    foo = bar . baz 

-- So if a type belongs to the specific class... 
instance Bar String where 
    bar = id 
    baz = id 

-- We can invoke the generic function on it. 
main :: IO() 
main = 
    putStrLn (foo "bar") 

(mã thực tế của tôi là cách phức tạp hơn, đây là một trường hợp luộc xuống tối thiểu để chứng minh mô hình.)

Nó không phải là rõ ràng với tôi lý do tại sao UndecidableInstances là cần thiết ở đây - các tham số loại a xuất hiện một lần ở cả hai bên của Bar a => Foo a, vì vậy tôi mong đợi mọi thứ để "chỉ làm việc". Tôi rõ ràng là thiếu một cái gì đó ở đây. Nhưng ở mức nào, có cách nào để làm điều này mà không cần sử dụng UndecidableInstances?

+2

Bằng cách khai báo 'instance Bar a => Foo a' bạn đã giới thiệu một cá thể cho mỗi' a' - ngữ cảnh 'Bar a' không được sử dụng trong trường hợp chọn - mặc dù nếu bạn có trường hợp chồng chéo GHC sẽ tìm thấy nhiều nhất cụ thể trên cơ sở mỗi mô-đun. –

+0

Đó là ... truy cập trực quan :-) Có cách nào không? Tôi giả định rằng "tìm kiếm một ví dụ cụ thể nhất" là những gì 'UndecidableInstances' sẽ cố gắng làm, nhưng trong mã của tôi nó đã thất bại thảm hại. –

+0

Liên quan: http://stackoverflow.com/questions/3213490 – sdcvvc

Trả lời

8

Có một số phương pháp bạn có thể thực hiện; Tôi không nghĩ rằng bạn đã cung cấp đủ ngữ cảnh để xác định ngữ cảnh nào phù hợp nhất. Nếu bạn đang sử dụng GHC-7.4, bạn có thể thử tiện ích mở rộng DefaultSignatures.

{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE DefaultSignatures #-} 

-- A generic class with a generic function. 
class Foo a where 
    foo :: a -> a 
    default foo :: Bar a => a -> a 
    foo = bar . baz 

-- A specific class with specific functions. 
class Bar a where 
    bar :: a -> a 
    baz :: a -> a 

instance Bar String where 
    bar = id 
    baz = id 

instance Foo String 

main :: IO() 
main = 
    putStrLn (foo "bar") 

Bạn vẫn cần phải tuyên bố rằng một loại là một thể hiện của Foo, nhưng bạn không cần phải lặp lại phương pháp kê khai vì việc thực hiện mặc định sẽ được sử dụng.

Một cách tiếp cận khá nhẹ khác là sử dụng loại mới. Nếu bạn có các hàm cần một cá thể Foo, bạn có thể bao bọc một phiên bản Bar trong loại mới.

newtype FooBar a = FooBar { unFooBar :: a } 

instance Bar a => Foo (FooBar a) where 
    foo = FooBar . bar . baz . unFooBar 

-- imported from a library or something... 
needsFoo :: Foo a => a -> b 

myFunc = needsFoo (FooBar someBar) 

Ngoài ra, bạn có thể nhận được bởi với thay foo với một chức năng bình thường, hoặc thực hiện một phiên bản đặc biệt cho Bar trường hợp:

-- if every `Foo` is also a `Bar`, you can just do this. No need for `Foo` at all! 
foo :: Bar a => a -> a 
foo = bar . baz 

-- if most `Foo`s aren't `Bar`s, you may be able to use this function when you have a `Bar` 
fooBar :: Bar a => a -> a 
foo = bar . baz 

Đây là có lẽ là giải pháp tốt nhất nếu họ làm việc cho hoàn cảnh của bạn.

Một tùy chọn khác là khai báo mỗi cá thể Foo thủ công. Mặc dù có thể có nhiều trường hợp khác nhau, nhưng nó khá phổ biến đối với các codebases chỉ có một số ít các cá thể thực sự được sử dụng. Nếu đó là sự thật ở đây, nó có lẽ ít công việc để chỉ viết ra 3 hoặc 4 trường hợp bạn cần thay vì cố gắng thực hiện một giải pháp tổng quát hơn.

Là phương sách cuối cùng, bạn có thể sử dụng mã như mã ban đầu, nhưng bạn cũng cần OverlappingInstances để làm cho nó hoạt động (nếu bạn không cần OverlappingInstances, thì bạn không cần lớp Foo). Đây là phần mở rộng cho phép GHC chọn "trường hợp cụ thể nhất" khi có nhiều kết quả phù hợp. Điều này sẽ làm việc nhiều hơn hoặc ít hơn, mặc dù bạn có thể không nhận được những gì bạn mong đợi.

class Foo a where 
    foo :: a -> a 

class Bar a where 
    bar :: a -> a 
    baz :: a -> a 

instance Bar String where 
    bar = id 
    baz = id 

instance Bar a => Foo a where 
    foo = bar . baz 

instance Foo [a] where 
    foo _ = [] 

main :: IO() 
main = 
    print (foo "foo") 

Bây giờ main in một chuỗi rỗng. Có hai trường hợp Foo, cho a[a]. Sau này là cụ thể hơn, vì vậy nó được chọn cho foo "foo" vì một chuỗi có loại [Char], mặc dù bạn có thể muốn trước đây. Vì vậy, bây giờ bạn cũng cần phải viết

instance Foo String where 
    foo = bar . baz 

tại thời điểm đó bạn cũng có thể bỏ hoàn toàn trường hợp Bar a => Foo a hoàn toàn.

+0

Wow, cảm ơn câu trả lời chi tiết! DefaultSignatures sẽ không làm việc cho tôi vì chúng yêu cầu khai báo mặc định trong lớp generic, và tôi muốn mọi người có thể tự do định nghĩa các lớp cụ thể mới. Cách tiếp cận newtype có thể làm việc cho tôi, tôi sẽ cần phải thích nghi nó với trường hợp phức tạp hơn của tôi. Chỉ cần sử dụng chức năng sẽ không làm việc cho tôi ... OverlappingInstances cũng có thể làm việc, như trong trường hợp của tôi có rất ít cơ hội chồng chéo của loại bạn mô tả. Nếu nó hoạt động, nó sẽ là tốt nhất bởi vì nó là ít xâm nhập nhất. Cảm ơn một lần nữa! –

+0

@ OrenBen-Kiki - Tôi không thể quan niệm bất kỳ cách nào mà chữ ký mặc định sẽ hạn chế sử dụng thêm 'Foo', cho dù nó liên quan đến cá thể, lớp con hay sử dụng phương thức' foo' trong các hàm không liên quan. Nó không rõ ràng chính xác những gì bạn đang cố gắng để mô hình ở đây, nhưng tôi nghi ngờ bạn đang cố gắng để thiết lập một hệ thống phân cấp kiểu OOP với các lớp kiểu. Nếu vậy, tôi đề nghị bạn hỏi một câu hỏi khác về SO yêu cầu các đề xuất về mô hình hóa vấn đề của bạn trong Haskell. –

+0

Ngoài ra một lời cảnh báo. Nếu bạn quyết định đi đường 'OverlappingInstances', tại một số điểm, GHC có thể phàn nàn rằng nó cần bật' IncoherentInstances'. Đừng làm thế, nó sẽ không giúp được gì. –

0

Ngoài câu trả lời ở trên. Chính trị được sử dụng trong mô hình Data.Traversable từ base thư viện rất hấp dẫn.Trong ngắn hạn, đưa ra ví dụ chung trong lực lượng thư viện người dùng cuối để chấp nhận quyết định của bạn, và điều này không phải luôn luôn là điều tốt nhất để làm. Data.Traversable chứa các chức năng như foldMapDefault, cho phép triển khai mặc định, nhưng quyết định triển khai cụ thể vẫn còn tùy thuộc vào người dùng.

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