2010-05-20 34 views
9

Nói rằng tôi có hai lớp kiểu định nghĩa như sau giống hệt nhau về chức năng nhưng khác nhau về tên:Linking/Kết hợp Loại Lớp học trong Haskell

class Monad m where 
    (>>=) :: m a -> (a -> m b) -> m b 
    return :: a -> m a 

class PhantomMonad p where 
    pbind :: p a -> (a -> p b) -> p b 
    preturn :: a -> p a 

Có cách nào để buộc hai loại cổ phiếu này lại với nhau để một cái gì đó một thể hiện của PhantomMonad sẽ tự động là một thể hiện của Monad, hoặc các cá thể cho mỗi lớp phải được viết một cách rõ ràng? Bất kỳ thông tin chi tiết nào sẽ được đánh giá cao nhất, cảm ơn!

+2

Là «preturn :: a -> p b' một lỗi đánh máy? –

Trả lời

13

Câu trả lời hay: Không, điều bạn đang hy vọng thực hiện không thực sự khả thi. Bạn có thể viết một thể hiện giống như nó làm những gì bạn muốn, có thể cần một số phần mở rộng GHC trong tiến trình, nhưng nó sẽ không hoạt động theo cách bạn muốn.

Câu trả lời không chính xác: Bạn có thể hoàn thành những gì bạn muốn bằng cách sử dụng siêu lập trình loại cấp độ đáng sợ, nhưng nó có thể phức tạp. Điều này thực sự không được khuyến nghị trừ khi bạn hoàn toàn cần điều này để làm việc vì một lý do nào đó.

Các trường hợp chính thức không thể thực sự phụ thuộc vào các trường hợp khác, vì GHC chỉ xem xét "trường hợp đầu" khi đưa ra quyết định và hạn chế về lớp nằm trong "ngữ cảnh". Để làm một cái gì đó giống như một "loại từ đồng nghĩa" ở đây, bạn sẽ phải viết những gì trông giống như một thể hiện của Monad cho tất cả các loại có thể, mà rõ ràng là không có ý nghĩa. Bạn sẽ được chồng chéo với các phiên bản khác của Monad, trong đó có vấn đề riêng của mình. Trước hết, tôi không nghĩ rằng một trường hợp như vậy sẽ đáp ứng các yêu cầu kiểm tra chấm dứt đối với độ phân giải ví dụ, vì vậy bạn cũng cần mở rộng UndecidableInstances, có nghĩa là khả năng viết các trường hợp sẽ gửi loại GHC kiểm tra vào một vòng lặp vô hạn.

Nếu bạn thực sự muốn đi xuống hố thỏ đó, hãy duyệt qua trên Oleg Kiselyov's website một chút; anh ấy là một vị thánh bảo trợ của chương trình siêu lập trình loại ở Haskell.

Đó là công cụ thú vị, để chắc chắn, nhưng nếu bạn chỉ muốn viết mã và làm cho nó hoạt động, có lẽ không đáng để giảm đau.

Chỉnh sửa: Được rồi, tôi đã phóng đại vấn đề ở đây. Một cái gì đó như PhantomMonad hoạt động tốt như một lần duy nhất và nên làm những gì bạn muốn, với các tiện ích mở rộng Overlapping - và UndecidableInstances GHC. Những thứ phức tạp bắt đầu khi bạn muốn làm bất cứ điều gì phức tạp hơn những gì trong câu hỏi. Lời cảm ơn chân thành của tôi đến Norman Ramsey đã gọi tôi về điều đó - tôi thực sự nên biết rõ hơn.

Tôi vẫn không thực sự khuyên bạn nên thực hiện loại điều này mà không có lý do chính đáng, nhưng nó không tệ như tôi đã tạo ra âm thanh. Mea culpa.

+0

Cảm ơn! Tôi đã tò mò nếu tôi chỉ thiếu một cái gì đó hiển nhiên với suy nghĩ phức tạp của tôi hoặc nếu điều này thực sự là hơi say lên. Không cần phải nói, tôi đang gắn bó với câu trả lời "Tốt". – thegravian

+0

@thegravian: Một quyết định khôn ngoan. Nếu nó giúp, ý tưởng của bạn không phải là vô lý, nó chỉ là không khả thi cho hệ thống loại lớp của Haskell khi nó đứng. Tôi nghĩ rằng đã có một số phần mở rộng được đề xuất sẽ làm cho nó hoạt động, nhưng không có phần mở rộng nào được triển khai cho đến nay. –

+0

@camcann: Giải pháp không thực sự là * đáng sợ, phải không?Ý tôi là, từ "không thể giải quyết" là một chút đáng sợ, nhưng kiểm tra kiểu Haskell đã hoàn thành cho thời gian theo cấp số nhân, vì vậy tôi sẽ không để nó ngăn tôi làm điều gì đó mà tôi thực sự muốn làm ... –

6

Đó là một thiết kế khác thường. Bạn có thể không chỉ cần loại bỏ PhantomMonad, vì nó là đẳng cấu cho lớp khác.

+0

Bạn nói đúng, thiết kế không khả thi (và thực sự là vô nghĩa). Tôi quyết định tránh làm điều này, nhưng vẫn còn tò mò nếu Haskell có một số loại phương tiện để làm điều này và tôi đã bỏ lỡ chúng. Nó có lẽ không, bởi vì đây là điều mà mọi người lành mạnh sẽ không làm. – thegravian

+3

trường hợp tương tự sẽ là 'Monl''s và' biến' 'MonadTrans' lớp. chúng giống hệt nhau. một câu trả lời về cách hợp nhất các lớp (có thể là nó) trong ví dụ bí truyền đơn giản này cũng có thể hữu ích cho mã thực sự. – yairchu

+2

@yairchu: Để công bằng, giải pháp "loại bỏ một lớp" của Don là giải pháp đúng cho trường hợp đó, cụ thể là xóa 'mtl' ... –

1

Mặc dù điều này không thực sự có ý nghĩa, hãy thử

instance Monad m => PhantomMonad m where 
    pbind = (>>=) 
    preturn = return 

(có thể với một số cảnh báo trình biên dịch ngừng hoạt động).

+0

Đó là cách khác xung quanh từ những gì ông yêu cầu, mặc dù. Tất nhiên, 'instance (PhantomMonad m) => Monad m trong đó ...' sẽ gây ra nhiều vấn đề hơn nữa. –

7

Có cách nào để buộc hai lớp này lại với nhau sao cho một cái gì đó là một thể hiện của PhantomMonad sẽ tự động là một thể hiện của Monad?

Có, nhưng nó đòi hỏi sự mở rộng ngôn ngữ hơi đáng báo động FlexibleInstancesUndecidableInstances:

instance (PhantomMonad m) => Monad m where 
    return = preturn 
    (>>=) = pbind 

FlexibleInstances không phải là quá xấu, nhưng nguy cơ undecidability là hơi đáng báo động hơn. Vấn đề là trong quy tắc suy luận, không có gì nhận được nhỏ hơn, vì vậy nếu bạn kết hợp khai báo cá thể này với một khai báo tương tự khác (như nói hướng ngược lại), bạn có thể dễ dàng lấy trình kiểm tra kiểu lặp mãi mãi.

Tôi thường thoải mái khi sử dụng FlexibleInstances, nhưng tôi có xu hướng tránh UndecidableInstances mà không có lý do rất tốt. Ở đây tôi đồng ý với đề nghị của Don Stewart rằng bạn nên sử dụng Monad để bắt đầu. Nhưng câu hỏi của bạn là nhiều hơn trong bản chất của một thí nghiệm suy nghĩ, câu trả lời là bạn có thể làm những gì bạn muốn mà không cần phải đi vào một mức độ Oleg của sự sợ hãi.

+0

Điều này không có tác dụng vì bạn đã chồng chéo lên nhau mỗi phiên bản 'Monad' khác . Nhưng sau khi chơi xung quanh cố gắng để phá vỡ các công cụ, có vẻ như là GHC thông minh hơn tôi nghĩ với các lớp kiểu tham số đơn. Rõ ràng là bạn chỉ nhận được một cá thể chung như thế này, nhưng như một lần, điều này làm việc tốt cho 'OverlappingInstances'. –

3

Một giải pháp khác là sử dụng newtype. Đây không phải là chính xác những gì bạn muốn, nhưng thường được sử dụng trong những trường hợp như vậy.

Điều này cho phép liên kết các cách khác nhau để chỉ định cùng cấu trúc. Ví dụ: ArrowApply (từ Control.Arrow) và Monad là tương đương. Bạn có thể sử dụng Kleisli để tạo một ArrowApply ra khỏi một đơn nguyên và ArrowMonad để tạo một đơn lẻ ra khỏi ArrowApply.

Ngoài ra, trình bao bọc một chiều là có thể: WrapMonad (trong Control.Applicative) tạo thành một ứng dụng ra khỏi một đơn nguyên.

class PhantomMonad p where 
    pbind :: p a -> (a -> p b) -> p b 
    preturn :: a -> p a 

newtype WrapPhantom m a = WrapPhantom { unWrapPhantom :: m a } 

newtype WrapReal m a = WrapReal { unWrapReal :: m a } 

instance Monad m => PhantomMonad (WrapPhantom m) where 
    pbind (WrapPhantom x) f = WrapPhantom (x >>= (unWrapPhantom . f)) 
    preturn = WrapPhantom . return 

instance PhantomMonad m => Monad (WrapReal m) where 
    WrapReal x >>= f = WrapReal (x `pbind` (unWrapReal . f)) 
    return = WrapReal . preturn 
+1

Không thể tìm thấy một 'WrapMonad' trong' Control.Arrow' nhưng 'instance ArrowApply a => Monad (a())'. 'newtype Kleisli m a b = Kleisli (a -> m b)'; 'instance Monad m => ArrowApply (Kleisli m)'; Thực hiện một chuyến đi khứ hồi thông qua các trường hợp này và bạn không kết thúc ở cùng một 'ArrowApply' mà bạn đã bắt đầu. 'pre a x y = a x y',' đăng a x y = x -> a() y'. – yairchu

+0

Rất tiếc, ý tôi là ArrowMonad. – sdcvvc

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