存在类型和单子转换器
上下文:我试图生成一个错误单子,它还跟踪警告列表,如下所示:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
存在主义可能不是你想要的。无法“观察”
Dangerous a
值中绑定到e
或w
的实际类型,因此您完全受限于Error
和Show
为您提供的操作。换句话说,您对
w
唯一了解的是您可以将其转换为String
,因此它也可能只是一个String
> (为了简化而忽略优先级),而您对e
唯一了解的是,您可以将其转换为String
,您可以将String
code>s 进去,你就拥有了它的独特价值(noMsg
)。无法断言或检查这些类型是否与其他类型相同,因此一旦将它们放入Dangerous
中,就无法恢复这些类型可能具有的任何特殊结构。错误消息的意思是,本质上,您的
runDangerous
类型声称您可以将Dangerous
转换为(Either ea, [w])< /code> 对于具有相关实例的任何
e
和w
。这显然是不正确的:您只能将Dangerous
转换为该类型,一个选择e
和w
>:它是用它创建的。w1
只是因为你的Dangerous
类型是用类型变量w
定义的,runDangerous
也是如此,所以GHC 重命名了其中之一以避免名称冲突。您需要为
runDangerous
提供的类型如下所示:给定一个函数,该函数将接受
(Either ea, [w])
类型的值 any 选择e
和w
,只要它们有给定的实例,并且Dangerous a
会产生该函数的结果。这很难让你头脑清醒!实现非常简单,
只需对您的版本进行微不足道的更改即可。如果这对你有用,那就太好了;但我怀疑存在主义是否是实现你想做的事情的正确方法。
请注意,您需要使用
{-# LANGUAGE RankNTypes #-}
来表示runDangerous
的类型。或者,您可以为您的结果类型定义另一个存在式:并使用
case
提取结果,但您必须小心,否则 GHC 会开始抱怨您已经让e< /code> 或
w
escape — 这相当于尝试将不充分多态的函数传递给其他形式的runDangerous
;即需要对e
和w
进行更多限制,超出了runDangerous
类型所保证的范围。An existential is probably not what you want here; there is no way to "observe" the actual types bound to
e
orw
in aDangerous a
value, so you're completely limited to the operations given to you byError
andShow
.In other words, the only thing you know about
w
is that you can turn it into aString
, so it might as well just be aString
(ignoring precedence to simplify things), and the only thing you know aboute
is that you can turn it into aString
, you can turnString
s 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 aDangerous
, 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 aDangerous
into an(Either e a, [w])
for anye
andw
that have the relevant instances. This clearly isn't true: you can only turn aDangerous
into that type for one choice ofe
andw
: the one it was created with. Thew1
is just because yourDangerous
type is defined with a type variablew
, and so isrunDangerous
, so GHC renames one of them to avoid name clashes.The type you need to give
runDangerous
looks like this:which, given a function which will accept a value of type
(Either e a, [w])
for any choices ofe
andw
so long as they have the instances given, and aDangerous a
, produces that function's result. This is quite hard to get your head around!The implementation is as simple as
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 ofrunDangerous
. Alternatively, you can define another existential for your result type:and extract the result with
case
, but you'll have to be careful, or GHC will start complaining that you've lete
orw
escape — which is the equivalent of trying to pass an insufficiently polymorphic function to the other form ofrunDangerous
; i.e. one that requires more constraints on whate
andw
are beyond what the type ofrunDangerous
guarantees.好吧,我想我明白了我在困惑之后的情况:(
instance Monad Dangerous
和data DangerousT
也有帮助。)这允许您拥有以下代码:
然后在您可以在主模块中定义
Show Failure
、Error Failure
和Show warning
的自定义实例,并采用集中方式来格式化错误消息,例如,在我看来,这是一个漂亮的处理错误和警告的酷方法。我有一个类似这样的工作模块,现在我要完善它,也许把它放在 hackage 上。建议表示赞赏。
Ok, I think I figured out what I was floundering after:
(
instance Monad Dangerous
anddata DangerousT
help too.)This allows you to have the following code:
and then in your main module you may define custom instances of
Show Failure
,Error Failure
, andShow Warning
and have a centralized way to format your error messages, for exampleWhich, 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.