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?
An existential is probably not what you want here; there is no way to “observe” the actual types bound to
eorwin aDangerous avalue, so you’re completely limited to the operations given to you byErrorandShow.In other words, the only thing you know about
wis 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 abouteis that you can turn it into aString, you can turnStrings 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
runDangerousclaims that you can turn aDangerousinto an(Either e a, [w])for anyeandwthat have the relevant instances. This clearly isn’t true: you can only turn aDangerousinto that type for one choice ofeandw: the one it was created with. Thew1is just because yourDangeroustype 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
runDangerouslooks like this:which, given a function which will accept a value of type
(Either e a, [w])for any choices ofeandwso 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 leteorwescape — 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 whateandware beyond what the type ofrunDangerousguarantees.