7

上下文:我正在尝试生成一个错误单子,它还跟踪警告列表,如下所示:

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

ieDangerous a是导致(Either e a, [w])wheree是可显示的错误并且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

4

2 回答 2

12

存在主义可能不是你想要的。无法“观察”绑定到值e或值w中的实际类型,因此您完全受限于andDangerous a提供给您的操作。ErrorShow

换句话说,你唯一知道w的是你可以把它变成a String,所以它还不如只是a String(忽略优先级以简化事情),你唯一知道e的是你可以把它变成a String,你可以把Strings 变成它,你就有了它的可区分值(noMsg)。无法断言或检查这些类型是否与其他类型相同,因此一旦将它们放入 aDangerous中,就无法恢复这些类型可能具有的任何特殊结构。

错误消息的意思是,本质上,您的类型 forrunDangerous声称您可以将 aDangerous转换为 a (Either e a, [w])for any e并且w具有相关实例。这显然是不正确的:您只能将 aDangerous转换为该类型以选择eand w:创建它的那个。这w1只是因为您的Dangerous类型是用类型变量定义的wrunDangerous所以 GHC 重命名其中一个以避免名称冲突。

您需要提供的类型runDangerous如下所示:

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

其中,给定一个函数,该函数将接受任何选择的类型值,(Either e a, [w])只要它们具有ew定的实例和 a 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保证有更多限制。

于 2011-12-22T18:56:38.373 回答
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 FailureError FailureShow Warning有一个集中的方式来格式化您的错误消息,例如

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

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

在我看来,这是一种处理错误和警告的非常酷的方法。我有一个类似这样的工作模块,现在我要去完善它,也许把它放在hackage上。建议表示赞赏。

于 2011-12-23T02:34:24.733 回答