Inductive lifting

Viewed 87

I would like to combine the various lifts into a single class

class Lift a b where
  lift :: a -> b

So that lift can be used in place of fmap, liftA2, liftA3, etc.

Now it is easy enough to write the instances for these:

instance Functor f => Lift (a -> b) (f a -> f b) where
  lift = fmap
instance Applicative f => Lift (a -> b -> c) (f a -> f b -> f c) where
  lift = liftA2
instance Applicative f => Lift (a -> b -> c -> d) (f a -> f b -> f c -> f d) where
  lift = liftA3

However for liftA2 and beyond there is an inductive property. That is we can derive a lift from smaller lifts. For example:

liftA2 f a b       = (liftA  f a)       <*> b
liftA3 f a b c     = (liftA2 f a b)     <*> c
liftA4 f a b c d   = (liftA3 f a b c)   <*> d
liftA5 f a b c d e = (liftA4 f a b c d) <*> e
...

So I would like to instead of enumerating each lift as a separate instances use induction to make all of them. However while the definition is inductive I have trouble expressing the relationship between one lift and the next algebraically. That is while I can describe it, it is not a combination of lifts and simple combinators.

Even if I rewwrite them point free we can see that each level is more complex that the last:

liftA2 = ((<*>) .) . liftA
liftA3 = (((<*>) .) .) . liftA2
liftA4 = ((((<*>) .) .) .) . liftA3
liftA5 = (((((<*>) .) .) .) .) . liftA4
...

Now I think that this could be worked around by adding a ghc natural to the class

class Lift (n :: Nat) (a :: Type) (b :: Type) | a b -> n  where
  lift :: a -> b

And then using a supporting type class to do a lot of the connective work. However this solution would be far from elegant and I view it mostly as a last resort since I would rather no bloat the class with a Nat used only for computation.

Is there a way I inductively define lift without changing the class definition like this?

0 Answers
Related