2011-12-22 44 views
7

語境:我想產生一個錯誤的單子也不斷警告的列表的軌道,這樣的事情:生存類型和單子變壓器

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

Dangerous a是導致(Either e a, [w]),其中e操作是一個可顯示的錯誤,並且w是可顯示的。

問題是,我似乎無法真正運行該事情,主要是因爲我不明白存在類型。注意:

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

這並不編譯,因爲:

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]) 

我迷路了。什麼是w1?爲什麼我們不能推斷它是~ w

回答

12

一個存在可能不是你想要的;沒有辦法「觀察」與ew綁定的實際類型的值,因此您完全限於ErrorShow給予您的操作。

換句話說,你唯一知道的關於w是,你可以把它變成一個String,所以它也可能只是一個String(忽略優先,以簡化的東西),唯一的事情,你知道e是你可以把它變成String,你可以把String寫入它,並且你有一個明顯的值(noMsg)。無法斷言或檢查這些類型是否與其他類型相同,因此一旦將它們放入Dangerous中,就無法恢復這些類型可能具有的任何特殊結構。

什麼錯誤消息說的是,從本質上講,你的類型runDangerous索賠,你可以把Dangerous(Either e a, [w])任何ew有相關的實例。這顯然是不正確的:您只能將Dangerous轉換爲一個選擇ew:它創建的那個。 w1只是因爲您的Dangerous類型是使用類型變量w以及runDangerous來定義的,所以GHC將其中的一個重命名以避免名稱衝突。

你需要給runDangerous的類型如下:

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

其中,由於將接受(Either e a, [w])類型的值e任何選擇和w,只要他們有一個功能給出的實例和一個Dangerous a,產生該函數的結果。這很難讓你的頭腦!

的實現很簡單,只要

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

這是您的版本一個微不足道的變化。如果這對你有用,太好了;但我懷疑存在是實現你想要做的任何事的正確方法。

請注意,您需要{-# LANGUAGE RankNTypes #-}來表示runDangerous的類型。或者,你可以定義另一個存在於你的結果類型:

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) [] 

case提取結果,但你必須要小心,或GHC會開始抱怨,你已經讓ew逃生 - 這相當於試圖將不充分的多態函數傳遞給另一種形式的runDangerous;即對ew的要求超出runDangerous保證類型的限制。

+0

有沒有更好的(或更習慣的)方式來做到這一點?我真的只想要一個帶有一些警告的錯誤monad。 – So8res 2011-12-22 19:43:42

+0

爲什麼不直接將類型定義爲「Dangerous e w a」?如果我明白你想要達到的目標(我很可能不會這樣做),這裏沒有必要存在這種存在。 – ehird 2011-12-22 19:45:09

+0

我有幾個模塊都會拋出自己的錯誤和警告,並且它們在頂層處理。頂層只需要打印它們,但是在模板文件中選擇模塊和危險模板錯誤模板警告模板時會出現'Dangerous OptError OptWarning [Option]'和'Dangerous TemplateError TemplateWarning Template',當它們全都是'show'n時很煩人。我試圖刪除大量的樣板並學習一些東西,這當然不是必需的。 – So8res 2011-12-22 19:48:40

1

好吧,我想我想通了,我一直在掙扎後:(instance Monad Dangerousdata DangerousT幫助太)

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, []) 

這可以讓你有下面的代碼:

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 
... 

然後在您的主模塊中,您可以定義Show Failure,Error FailureShow Warning的自定義實例第二必須格式化你的錯誤消息的集中方式,例如

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

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

其中,在我看來,是一個很酷的方式來處理錯誤和警告。我有一個工作模塊,就是這樣的,現在我已經開始打磨它,並且可能把它放在黑客身上。建議感激。

+1

對不起,但'data Warning = forall w。 (Show w)=>警告w'等同於'data Warning = Warning String';你可能會在警告值上使用'warn'函數調用'show'。在這裏,存在實際上不是你想要的。 – ehird 2011-12-23 12:24:27

+1

我很欣賞你正在嘗試應用一個很酷的類型系統功能,但是這裏唯一的影響是不增加任何功能就使代碼更復雜。 – ehird 2011-12-23 12:30:44

+0

這裏的一些更多的材料,以幫助解釋:[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 /) - 我已經很好地解釋了爲什麼不使用'data Foo = forall a。 (顯示a)=> Foo a',但不幸的是現在找不到鏈接。 – ehird 2011-12-23 14:04:48