I have a pluggable runtime type checker that supports parametric but no ad-hoc polymorphism, because there is no compiling step and type information are erased as soon as the type checker is deactivated.
Now I recently came up with the idea to reify type classes with an explicit type, so that I gain some of their advantages without having to fully incorporate the underlying mechanism into the type checker:
data Functor f = Functor {fmap :: forall a b. (a -> b) -> f a -> f b}
mapList = Functor map
fmap (mapList) (+1) [1,2,3]
It seems as if type classes can be simulated with rank-2 types, at least at the type level, since, of course, there is still no static dispatching.
Is my assumption right and since I am a Haskell rookie, does my explicit functor type involves any advantages over just using map
directly?
The idea of representing a class by a data type is basically a dictionary, which is in fact pretty much how GHC implements type classes anyway: a constrained-polymorphic function/value
f :: Functor f => Y
is at runtime represented by a function
_f_ :: FunctorDict f -> Y
where FunctorDict
is essentially your Functor
Rank2-ADT (or GADT).
The main thing that's special about actual type classes is that these dictionaries have the singleton property: for every type constructor F
, there can only ever be exactly one instance Functor F
, whereas you can in principle have multiple different FunctorDict F
values. That can at times actually be an an advantage, but often it's just a burden because you need to explicitly carry those dictionaries around (GHC can just pick them automatically, because the choice is unambiguous), and it's harder to state laws.