存在类型和monad变换器

时间:2011-12-22 18:49:08

标签: haskell monads monad-transformers existential-type

上下文:我正在尝试生成一个错误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

2 个答案:

答案 0 :(得分:12)

存在主义可能不是你想要的;无法“观察”e值中绑定到wDangerous a的实际类型,因此您完全受限于{{1}给您的操作}和Error

换句话说,您对Show唯一了解的是,您可以将其转换为w,因此它也可能只是String(忽略优先级以简化事情),你唯一知道的String是你可以把它变成e,你可以将String变成它,你有一个显着的价值( String)。没有办法断言或检查这些类型是否与其他类型相同,因此一旦将它们放入noMsg,就无法恢复这些类型可能具有的任何特殊结构。

错误消息的含义是,基本上,Dangerous的类型声称您可以将runDangerous转换为Dangerous 任何 { {1}}和(Either e a, [w])具有相关实例。这显然不是这样的:您只能将e转换为{em>一个选择wDangerous的类型:创建它的那个。{1}}。 e只是因为您的w类型定义了类型变量w1,因此Dangerous也是如此,因此GHC会重命名其中一个以避免名称冲突。

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

runDangerous

,给定一个函数,只要它们有给出的实例,它就会接受 runDangerousrunDangerous :: (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;即需要对wrunDangerous超出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 Dangerousdata 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上。建议表示赞赏。