存在类型和单子转换器

发布于 2024-12-22 16:02:12 字数 1063 浏览 4 评论 0原文

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

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

Dangerous a 是一个导致 (Either ea, [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

Context: I'm trying to produce an error monad that also keeps track of a list of warnings, something like this:

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

i.e. Dangerous a is an operation resulting in (Either e a, [w]) where e is a showable error and w is showable.

The problem is, I can't seem to actually run the thing, mostly because I don't understand existential types all that well. Observe:

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

This doesn't compile, because:

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

I'm lost. What's w1? Why can't we deduce that it's ~ w?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

意中人 2024-12-29 16:02:12

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

换句话说,您对 w 唯一了解的是您可以将其转换为 String,因此它也可能只是一个 String > (为了简化而忽略优先级),而您对 e 唯一了解的是,您可以将其转换为 String,您可以将 String code>s 进去,你就拥有了它的独特价值(noMsg)。无法断言或检查这些类型是否与其他类型相同,因此一旦将它们放入Dangerous中,就无法恢复这些类型可能具有的任何特殊结构。

错误消息的意思是,本质上,您的 runDangerous 类型声称您可以将 Dangerous 转换为 (Either ea, [w])< /code> 对于具有相关实例的任何 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 ea, [w]) 类型的值 any 选择ew,只要它们有给定的实例,并且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 会开始抱怨您已经让 e< /code> 或 w escape — 这相当于尝试将不充分多态的函数传递给其他形式的 runDangerous;即需要对 ew 进行更多限制,超出了 runDangerous 类型所保证的范围。

An existential is probably not what you want here; there is no way to "observe" the actual types bound to e or w in a Dangerous a value, so you're completely limited to the operations given to you by Error and Show.

In other words, the only thing you know about w is that you can turn it into a String, so it might as well just be a String (ignoring precedence to simplify things), and the only thing you know about e is that you can turn it into a String, you can turn Strings into it, and you have a distinguished value of it (noMsg). There is no way to assert or check that these types are the same as any other, so once you put them into a Dangerous, there's no way to recover any special structure those types may have.

What the error message is saying is that, essentially, your type for runDangerous claims that you can turn a Dangerous into an (Either e a, [w]) for any e and w that have the relevant instances. This clearly isn't true: you can only turn a Dangerous into that type for one choice of e and w: the one it was created with. The w1 is just because your Dangerous type is defined with a type variable w, and so is runDangerous, so GHC renames one of them to avoid name clashes.

The type you need to give runDangerous looks like this:

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

which, given a function which will accept a value of type (Either e a, [w]) for any choices of e and w so long as they have the instances given, and a Dangerous a, produces that function's result. This is quite hard to get your head around!

The implementation is as simple as

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

which is a trivial change to your version. If this works for you, great; but I doubt that an existential is the right way to achieve whatever you're trying to do.

Note that you'll need {-# LANGUAGE RankNTypes #-} to express the type of runDangerous. Alternatively, you can define another existential for your result type:

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

and extract the result with case, but you'll have to be careful, or GHC will start complaining that you've let e or w escape — which is the equivalent of trying to pass an insufficiently polymorphic function to the other form of runDangerous; i.e. one that requires more constraints on what e and w are beyond what the type of runDangerous guarantees.

剪不断理还乱 2024-12-29 16:02:12

好吧,我想我明白了我在困惑之后的情况:(

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 上。建议表示赞赏。

Ok, I think I figured out what I was floundering after:

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 and data DangerousT help too.)

This allows you to have the following code:

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

and then in your main module you may define custom instances of Show Failure, Error Failure, and Show Warning and have a centralized way to format your error messages, for example

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

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

Which, in my opinion, is a pretty cool way to handle errors and warnings. I've got a working module that's something like this, now I'm off to polish it up and maybe put it on hackage. Suggestions appreciated.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文