Is it possible to do heterogeneous Data.Map
in Haskell with GADT
instead of Dynamic
? I tried to model heterogeneous collection as laid out in this answer:
{-# LANGUAGE GADTs #-}
class Contract a where
toString :: a -> String
data Encapsulated where
Encapsulate :: Contract a => a -> Encapsulated
getTypedObject :: Encapsulated -> a
getTypedObject (Encapsulate x) = x
The idea being that Encapsulated
could be used to store different objects of TypeClass a
, and then extracted at run-time for specific type.
I get error about type of x
not matching Contract a
. Perhaps I need to specify some kind of class constraints to tell GHC
that type of x
in Encapsulate x
is same as a
in Contract a
?
T.hs:10:34:
Couldn't match expected type ‘a’ with actual type ‘a1’
‘a1’ is a rigid type variable bound by
a pattern with constructor
Encapsulate :: forall a. Contract a => a -> Encapsulated,
in an equation for ‘getTypedObject’
at T.hs:10:17
‘a’ is a rigid type variable bound by
the type signature for getTypedObject :: Encapsulated -> a
at T.hs:9:19
Relevant bindings include
x :: a1 (bound at T.hs:10:29)
getTypedObject :: Encapsulated -> a (bound at T.hs:10:1)
In the expression: x
In an equation for ‘getTypedObject’:
getTypedObject (Encapsulate x) = x
I am trying this approach because I have JSON objects of different types, and depending on the type that is decoded at run-time over the wire, we want to retrieve appropriate type-specific builder
from Map
(loaded at run-time IO in main
from configuration files, and passed to the function) and pass it decoded JSON data of the same type.
Dynamic
library would work here. However, I am interested in finding out if there are other possible approaches such as GADTs
or datafamilies
.
your problem is that you push the a
out again (which will not work) - what you can do is using the contract internally like this:
useEncapsulateContract :: Encapsulated -> String
useEncapsulateContract (Encapsulate x) = toString x
basically the compiler is telling you everything you need to know: inside you have a forall a. Contract a
(so basically a constraint a
to be a Contract
)
On getTypedObject :: Encapsulated -> a
you don't have this constraint - you are telling the compiler: "look this works for every a
I'll demand"
To get it there you would have to parametrize Encapsulated
to Encapsulated a
which you obviously don't want.
The second version (the internal I gave) works because you have the constraint on the data-constructor and so you can use it there
to extent this a little:
this
getTypedObject :: Contract a => Encapsulated -> a
getTypedObject (Encapsulate x) = x
wouldn't work either as now the you would have Contract a
but still it could be two different types which just share this class.
And to give hints to the compiler that both should be the same you would have to parametrize Encapsulate
again ....
right now by doing this:
Encapsulate :: Contract a => a -> Encapsulated
you erase that information