2011-12-22 28 views
7

Bối cảnh: Tôi đang cố gắng để tạo ra một đơn nguyên lỗi mà cũng theo dõi danh sách cảnh báo, một cái gì đó như thế này:loại Hiện sinh và máy biến áp đơn nguyên

data Dangerous a = forall e w. (Error e, Show e, Show w) => 
    Dangerous (ErrorT e (State [w]) a) 

tức Dangerous a là một hoạt động dẫn đến (Either e a, [w]) nơi e là lỗi hiển thị và w có thể hiển thị.

Vấn đề là, tôi dường như không thể thực sự chạy điều này, chủ yếu là vì tôi không hiểu tất cả các loại tồn tại. Quan sát:

runDangerous :: forall a e w. (Error e, Show e, Show w) => 
    Dangerous a -> (Either e a, [w]) 
runDangerous (Dangerous f) = runState (runErrorT f) [] 

này không biên dịch, bởi vì:

Could not deduce (w1 ~ w) 
from the context (Error e, Show e, Show w) 
... 
`w1' is a rigidtype variable bound by 
    a pattern with constructor 
    Dangerous :: forall a e w. 
       (Error e, Show e, Show w) => 
       ErrorT e (State [w]) a -> Dangerous a 
... 
`w' is a rigid type variable bound by 
    the type signature for 
    runDangerous :: (Error e, Show e, Show w) => 
        Dangerous a -> (Either e a, [w]) 

Tôi bị mất. W1 là gì? Tại sao chúng ta không thể suy ra rằng đó là ~ w?

Trả lời

12

Hiện trạng có thể không phải là những gì bạn muốn ở đây; không có cách nào để "quan sát" các loại thực tế được ràng buộc với e hoặc w trong giá trị Dangerous a, vì vậy, bạn hoàn toàn bị giới hạn đối với các hoạt động được cung cấp cho bạn bởi ErrorShow.

Nói cách khác, điều duy nhất bạn biết về w là bạn có thể biến nó thành một String, vì vậy nó có thể cũng chỉ là một String (bỏ qua ưu tiên để đơn giản hóa mọi thứ), và điều duy nhất bạn biết về e là bạn có thể biến nó thành một String, bạn có thể biến String giây vào đó và bạn có một giá trị phân biệt của nó (noMsg). Không có cách nào để khẳng định hoặc kiểm tra xem các kiểu này có giống nhau hay không, vì vậy khi bạn đặt chúng vào một Dangerous, không có cách nào để phục hồi bất kỳ cấu trúc đặc biệt nào mà các kiểu đó có thể có.

gì được thông báo lỗi được nói là, về cơ bản, loại hình cho runDangerous tuyên bố rằng bạn có thể biến một Dangerous thành một (Either e a, [w]) cho bất kỳew rằng có những trường hợp có liên quan. Điều này rõ ràng không đúng: bạn chỉ có thể biến một loại Dangerous thành loại đó cho một lựa chọn ew: loại được tạo. Các w1 chỉ vì loại Dangerous của bạn được xác định với một biến loại w, và như vậy là runDangerous, do đó GHC đổi tên một trong số chúng để tránh xung đột tên.

Loại bạn cần phải cung cấp cho runDangerous trông như thế này:

runDangerous 
    :: (forall e w. (Error e, Show e, Show w) => (Either e a, [w]) -> r) 
    -> Dangerous a -> r 

đó, cho một chức năng mà sẽ chấp nhận một giá trị kiểu (Either e a, [w]) cho bất kỳ lựa chọn của ew chừng nào họ có các trường hợp đã cho và Dangerous a, tạo ra kết quả của hàm đó. Điều này là khá khó khăn để có được đầu của bạn xung quanh!

Việc thực hiện cũng đơn giản như

runDangerous f (Dangerous m) = f $ runState (runErrorT m) [] 

đó là một sự thay đổi không đáng kể đến phiên bản của bạn. Nếu điều này làm việc cho bạn, tuyệt vời; nhưng tôi nghi ngờ rằng một tồn tại là đúng cách để đạt được bất cứ điều gì bạn đang cố gắng làm.

Lưu ý rằng bạn cần {-# LANGUAGE RankNTypes #-} để thể hiện loại runDangerous. Ngoài ra, bạn có thể định nghĩa khác hiện sinh cho loại kết quả của bạn:

data DangerousResult a = forall e w. (Error e, Show e, Show w) => 
    DangerousResult (Either e a, [w]) 

runDangerous :: Dangerous a -> DangerousResult a 
runDangerous (Dangerous m) = DangerousResult $ runState (runErrorT m) [] 

và trích xuất kết quả với case, nhưng bạn sẽ phải cẩn thận, hoặc GHC sẽ bắt đầu phàn nàn rằng bạn đã để cho e hoặc w thoát - tương đương với việc cố gắng truyền một hàm đa hình không đủ cho dạng khác của runDangerous; tức là một yêu cầu có nhiều ràng buộc hơn về những gì ew vượt quá loại bảo đảm runDangerous.

+0

Có cách nào tốt hơn (hoặc thành ngữ hơn) để thực hiện việc này không? Tôi thực sự chỉ muốn một lỗi đơn lẻ đi kèm với một số cảnh báo. – So8res

+0

Tại sao không chỉ xác định loại là 'Nguy hiểm e w a'? Không cần thiết cho những tồn tại ở đây, nếu tôi hiểu những gì bạn đang cố gắng đạt được (điều mà tôi rất có thể không có). – ehird

+0

Tôi có một vài mô-đun mà tất cả đều ném lỗi và cảnh báo của riêng họ và chúng được xử lý ở cấp cao nhất. Cấp cao nhất chỉ cần in chúng, nhưng nó gây phiền nhiễu khi nói 'Dangerous OptError OptWarning [Option]' trong mô-đun tùy chọn và 'Template Template Template Template nguy hiểm trong mẫu template, khi tất cả chúng chỉ là' show'n. Tôi đang cố gắng để loại bỏ rất nhiều boilerplate và tìm hiểu một chút gì đó, nó chắc chắn không cần thiết. – So8res

1

Ok, tôi nghĩ rằng tôi đã tìm ra những gì tôi đã lúng túng sau: (. instance Monad Dangerousdata DangerousT giúp đỡ quá)

data Failure = forall e. (Error e, Show e) => Failure e 

data Warning = forall w. (Show w) => Warning w 

class (Monad m) => Errorable m where 
    warn :: (Show w) => w -> m() 
    throw :: (Error e, Show e) => e -> m() 

instance Errorable Dangerous where 
    warn w = Dangerous (Right(), [Warning w]) 
    throw e = Dangerous (Left $ Failure e, []) 

này cho phép bạn có đoạn mã sau:

foo :: Dangerous Int 
foo = do 
    when (badThings) (warn $ BadThings with some context) 
    when (worseThings) (throw $ BarError with other context) 

data FooWarning = BadThings FilePath Int String 
instance Show FooWarning where 
... 

và sau đó trong mô-đun chính của bạn, bạn có thể xác định các phiên bản tùy chỉnh của Show Failure, Error FailureShow Warning một nd có cách tập trung để định dạng thông báo lỗi của bạn, ví dụ:

instance Show Warning where show (Warning s) = "WARNING: " ++ show s 
instance Show Failure where ... 

let (result, warnings) = runDangerous function 
in ... 

Theo tôi, cách khá hay là xử lý lỗi và cảnh báo. Tôi đã có một mô-đun đang hoạt động giống như thế này, bây giờ tôi tắt để đánh bóng nó và có thể đặt nó vào hackage. Đề xuất được đánh giá cao.

+1

Xin lỗi, nhưng 'dữ liệu Cảnh báo = forall w. (Show w) => Warning w' tương đương với 'data Warning = Warning String'; bạn cũng có thể gọi hàm 'warn' của bạn' show' trên giá trị cảnh báo. Existentials là * thực sự không phải những gì bạn muốn * ở đây. – ehird

+1

Tôi đánh giá cao việc bạn đang cố áp dụng tính năng hệ thống kiểu thú vị, nhưng hiệu ứng duy nhất mà nó có ở đây là làm cho mã phức tạp hơn mà không cần thêm bất kỳ chức năng nào. – ehird

+0

Dưới đây là một số tài liệu khác để giúp giải thích: [1] (http://www.haskell.org/haskellwiki/FAQ#How_do_I_make_a_list_with_elements_of_different_types.3F), [2] (http://lukepalmer.wordpress.com/2010/01/ 24/haskell-antipattern-existential-typeclass /) - Tôi đã nhìn thấy một lời giải thích tốt về chính xác lý do tại sao không sử dụng một cái gì đó như 'dữ liệu Foo = forall a. (Hiển thị a) => Foo a', nhưng tiếc là không thể tìm thấy liên kết ngay bây giờ. – ehird

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