Data.Dynamic
has the following implementation:
data Dynamic where
Dynamic :: TypeRep a -> a -> Dynamic
It has occurred to me that the following definition would be equivalent (at least I think):
data Dynamic where
Dynamic :: Typeable a => a -> Dynamic
Because one can get from TypeRep a
to a Typeable
constraint using withTypeable
, and in the other direction, go from a Typeable
constraint to TypeRep a
using typeRep
.
I ask this question because I often have created GADTs with typeclass constraints, often to create "existential" types, and seeing this type has made me question whether instead of using typeclass constraints I should be adding a "witness" field? What should I consider when choosing between these two approaches?
Further thoughts...
Consider something like this:
data SillyListA m where
SillyListA :: Ord a => (a -> m ()) -> [a] -> SillyList m
data SillyListB m where
SillyListB :: (a -> a -> Ordering) -> (a -> m ()) -> [a] -> SillyList m
Here it being explicit about the argument instead of just including the typeclass constraint has a practical purpose, because you could have different orderings on the same type, and the second definition allows that without newtype silliness.
But that just seems silly where your type is basically a singleton as is the case in TypeRep a
.
I guess one slight benefit is that perhaps the witness can be made an argument to functions which means you don't have to use type applications to pull out the constructor.
So like with the first definition I can do:
f (Dynamic tr x) = ...
Instead of
f (Dynamic @a x) = ...
But what I find I do anyway is:
f (Dynamic @a _ x) = ...
Because the type is really handy to have in scope if f
has any sub-functions defined that are explicitly typed, and not many functions take TypeRep a
as an argument, they usually either require a type application or take Proxy @a
, so I end up needing the type in scope anyway.
I have started thinking about this because I have defined the type in my own code (please shout out if I've reinvented the wheel and this exists elsewhere):
data DynamicF f where
DynamicF :: forall (a :: Type) f. TypeRep a -> f a -> DynamicF f
And I defined it like this basically copying Dynamic
, but I'm thinking maybe just:
data DynamicF f where
DynamicF :: forall (a :: Type). Typeable a => f a -> DynamicF f
Is a better definition.
TypeRep
pattern synonym for the conversion. – GushertypeRep
is what I had to use. – GardieData.Dynamic
was written. IIRC it's older than type applications, sof (Dynamic @a x)
wasn't an option. I think it's even older thanScopedTypeVariables
. Back at that time, is there a good way to get a handle on the hidden type without a value-level witness? I'm genuinely unsure. – LianneTypeRep
directly:f (Dynamic rep a) | App fRep aRep <- rep, Just HRefl <- eqTypeRep fRep (typeRep @[]) = ...
. It can be nice to make the representation explicit, and then present a pattern synonym (as the two are equivalent) providing the alternative interface:pattern Dynamic' :: () => Typeable a => a -> Dynamic; pattern Dynamic' a = Dynamic TypeRep a
. – Elizebethelizondo