A map of polymorphic values in Haskell

Suppose I’ve got a class, which declares some constructors for values of its member types:

class C t where
  fromInt :: Int -> t
  fromString :: String -> t

Further suppose, I want to create a bunch of values using these polymorphic constructors and put them in a Map from containers package. But importantly, I want the values to remain polymorphic and deferr the decision on their concrete type until they are extracted from the map.

newtype WrapC = WrapC (forall t . C t => t)

newtype MapC = MapC (Map String WrapC)

Using RankNTypes extension I can define such a map and the following definition witnesses the fact that polymorphic values indeed can be extracted from it:

get :: C t => String -> MapC -> Maybe t
get key (MapC m) = fmap (\(WrapC t) -> t) $ Map.lookup key m

However, when I want to add a value to the map, I’m facing a type error, because Haskell apparently cannot unify some hidden type variables. The definition:

add :: C t => String -> t -> MapC -> MapC
add key val (MapC m) = MapC $ Map.insert key (WrapC val) m

fails to compile with:

• Couldn't match expected type ‘t1’ with actual type ‘t’
  ‘t1’ is a rigid type variable bound by
    a type expected by the context:
      forall t1. C t1 => t1
    at src/PolyMap.hs:21:53-55
  ‘t’ is a rigid type variable bound by
    the type signature for:
      add :: forall t. C t => String -> t -> MapC -> MapC

Intuitively I’m guessing that’s the type variable hidden inside WrapC that cannot be unified. What I don’t understand is why it needs to be. Or how can I make this example work…

>Solution :

You just need to hand your function something actually polymorphic:

add :: String -> (forall t. C t => t) -> MapC -> MapC

But I would propose something even dumber, maybe. Can you get away with this?

type MapC = Map String (Either Int String)

get :: C t => String -> MapC -> Maybe t
get k m = either fromInt fromString <$> Map.lookup k m

No type system extensions needed.

Leave a Reply