21
votes

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 HFunctorin 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?

1

1 Answers

2
votes

The HFunctor I tend to think of is (* -> *) -> * -> * -- i.e. a legitimate functor on functors. This has different characteristics than the one you're thinking of.

Here's how to define it, as well as the "monoidal" version of an applicative on it.

type Nat f g = forall a. f a -> g a

class HFunctor (f :: (* -> *) -> * -> *) where
    hfmap :: (Nat g h) -> Nat (f g) (f h)

data Prod f g a = Prod (f a) (g a)

class HFunctor f => HApplicative f where
    hpure  :: Nat g (f g)
    htensor :: Nat (Prod (f g) (f h)) (f (Prod g h))

I'll try to update later with some ideas about what this is and how to use it.

This isn't exactly what you're asking for, I realize, but I was inspired to try it out by your post.

I'd be interested in your specific use case as well.

To your two specific questions A) The HFunctor you describe has been described before on various occasions, I think by Gibbons in particular, but I don't know of it packaged up. I certainly haven't seen the Applicative before. B) I think you're stuck with the wrapper because we can't partially apply type synonyms.