It's indeed possible with existential types, but it's quite possibly a bad idea.
The way you would do it with an existential type is ConnWrapper
, like you said:
data ConnWrapper = forall conn. IConnection conn => ConnWrapper conn
That type is relatively straightforward: It's an IConnection
dictionary together with a value of a type compatible with it. If x :: conn
, and conn
is an instance of IConnection
, then ConnWrapper x :: ConnWrapper
. Given y :: ConnWrapper
, you know nothing about its value's type other than that it's an instance of IConnection
. So you can say case y of ConnWrapper z -> disconnect z
, but you can't say case y of ConnWrapper z -> z
-- there's no Haskell type you can give to that.
(ConnWrapper
is also an instance of IConnection
directly, and since the class is pretty simple, that means you pretty much never need to pattern-match on ConnWrapper
directly. You can just use disconnect y
and so on.)
If you were making the API, I'd strongly suggest reconsidering along the lines of the post I linked above, or at least understanding that approach. Many uses of ExistentialTypes in Haskell are actually more complicated than a non-existential approach. For example, you should see why data Foo = forall a. Show a => Foo a
is (almost) equivalent to String
. It looks like the HDBC may be intended to be used this way, though...