上下文:我正在尝试生成一个错误monad,它也会跟踪警告列表,如下所示:
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
?
答案 0 :(得分:12)
存在主义可能不是你想要的;无法“观察”e
值中绑定到w
或Dangerous a
的实际类型,因此您完全受限于{{1}给您的操作}和Error
。
换句话说,您对Show
唯一了解的是,您可以将其转换为w
,因此它也可能只是String
(忽略优先级以简化事情),你唯一知道的String
是你可以把它变成e
,你可以将String
变成它,你有一个显着的价值( String
)。没有办法断言或检查这些类型是否与其他类型相同,因此一旦将它们放入noMsg
,就无法恢复这些类型可能具有的任何特殊结构。
错误消息的含义是,基本上,Dangerous
的类型声称您可以将runDangerous
转换为Dangerous
任何 { {1}}和(Either e a, [w])
具有相关实例。这显然不是这样的:您只能将e
转换为{em>一个选择w
和Dangerous
的类型:创建它的那个。{1}}。 e
只是因为您的w
类型定义了类型变量w1
,因此Dangerous
也是如此,因此GHC会重命名其中一个以避免名称冲突。
您需要提供的类型w
如下所示:
runDangerous
,给定一个函数,只要它们有给出的实例,它就会接受 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
提取结果,但您必须小心,否则GHC会开始抱怨您让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
逃脱 - 这就相当于尝试将不完全多态的函数传递给另一种形式的e
;即需要对w
和runDangerous
超出e
类型保证的更多约束的约束。
答案 1 :(得分:1)
好的,我想我弄明白我在挣扎之后:
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, [])
(instance Monad Dangerous
和data DangerousT
也有帮助。)
这允许您拥有以下代码:
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 Failure
和Show Warning
的自定义实例,并采用集中的方式格式化错误消息,例如
instance Show Warning where show (Warning s) = "WARNING: " ++ show s
instance Show Failure where ...
let (result, warnings) = runDangerous function
in ...
在我看来,这是处理错误和警告的一种非常酷的方式。我有一个像这样的工作模块,现在我要把它弄清楚并且可能把它放在hackage上。建议表示赞赏。