I ran into a situation where my code would benefit from using Functor
and Applicative
-like abstractions, but for types of kind (* -> *) -> *
. Defining a higher-kinded functor can be done with RankNTypes
like this
class HFunctor f where
hfmap :: (forall x. a x -> b x) -> f a -> f b
But the higher kind version of Applicative
is a bit trickier. This is the best I could come up with:
class HFunctor f => HApplicative f where
hpure :: (forall x. a x) -> f a
(<**>) :: f (a :-> b) -> f a -> f b
newtype (:->) a b x = HFunc (a x -> b x)
infixr 5 :->
We need the :->
wrapper type in order to have functions with the kind * -> *
, but this doesn't let us nicely chain function application like we can with <$>
and <*>
for normal Applicatives. I can manage with helper such as
liftHA2 :: HApplicative f => (forall x. a x -> b x -> c x) -> f a -> f b -> f c
liftHA2 f fa fb = hpure (fun2 f) <**> fa <**> fb where
fun2 = HFunc . (HFunc .)
But it would be nice to have a general way to "lift" functions of any arity.
Some simple examples how the above instances can be used:
data Example f = Example (f Int) (f String)
instance HFunctor Example where
hfmap f (Example i s) = Example (f i) (f s)
instance HApplicative Example where
hpure a = Example a a
Example (HFunc fi) (HFunc fs) <**> Example i s = Example (fi i) (fs s)
e :: Example []
e = Example [1,2,3] ["foo", "bar"]
e' :: Example ((,) Int)
e' = hfmap (length &&& head) e -- Example (3,1) (2, "foo")
e'' :: Example []
e'' = liftHA2 (++) e e -- Example [1,2,3,1,2,3] ["foo", "bar", "foo", "bar"]
So, my question is: what are the above typeclasses called and are they already provided by some library in hackage? By googling I came up with Functor2
in linear-maps
and HFunctor
in multi-rec
but neither does exactly what I need.
Also, is there some way to write HApplicative
without the :->
wrapper or some other way to make function lifting easier?