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 lift
s 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?