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…
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.