I had a hard time putting together the question. Let's try step by step:
I have a Haskell type class (that represents a keyboard layout fyi):
class Data key => Palantype key where
-- the instance has to provide this mapping
keyCode :: key -> Char
-- but the reverse is required, too:
toKeys :: Char -> [key]
I can provide a (not-so-efficient) default implementation for toKeys based on Typeable and Data:
-- | override for efficiency
toKeys :: key -> Char -> [key]
toKeys k c =
let t = dataTypeOf k
ks = fromConstr . indexConstr t <$> [1..(maxConstrIndex t)]
m = foldl (\m k -> Map.insertWith ( ) (keyCode k) [k] m) Map.empty ks
in fromMaybe [] $ Map.lookup c m
... and the above code works. Nice.
However, there is a problem. The default implementation requires key as first argument, the reason being: Data requires a run-time representation of the type. This is provided by DataType, using dataTypeOf :: a -> DataType.
I have to adjust the type signature of toKeys and always provide a not-so-meaningful dummy key. I understand that this is how Data.Data works. But is there a way to get the same magic based on the type variable key?
There is the function typeRep in Data.Typeable that seems to work that way:
typeRep :: forall proxy a. Typeable a => proxy a -> TypeRep
But all the workings of Data (fromConstr, indexConstr, maxConstrIndex) rely on the run-time representation DataType (for a reason?).
An elegant solution using Data.Proxied:
toKeys :: Char -> [key]
toKeys c =
let t = dataTypeOfProxied (Proxy :: Proxy key)
ks = fromConstr . indexConstr t <$> [1..(maxConstrIndex t)]
m = foldl (\m k -> Map.insertWith ( ) (keyCode k) [k] m) Map.empty ks
in fromMaybe [] $ Map.lookup c m
CodePudding user response:
A quick test in GHCi:
> dataTypeOf (undefined :: Int)
DataType {tycon = "Prelude.Int", datarep = IntRep}
This reveals that dataTypeOf does not really need a runtime value, and the first argument is only used for its type. You can (and should) write something like
toKeys :: forall key . Data key => Char -> [key]
toKeys c =
let t = dataTypeOf (undefined :: key)
...
In my opinion, this interface is not how it should be today, but we still have it because of historical reasons. When Data was designed, I guess, we had no AllowAmbiguousTypes, TypeApplications so we used "unevaluated" arguments and/or proxies.
If Data were designed today, I guess we would have the ambiguous type
dataTypeOf :: forall a . Data a => DataType
and we would use that as dataTypeOf @key.
