diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+Version 1.4
+---------------
+* Added Rank2.Compose :: ((* -> *) -> *) -> (* -> *) -> ((* -> *) -> *)
+* Matched the precedence of <$> and <*> operators with Prelude
+* Relaxed the lower bound of base dependency to 4.10
+
 Version 1.3.2.1
 ---------------
 * Incremented the upper bound of the template-haskell dependency
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -208,4 +208,5 @@
 ~~~
 
 Grammars are another use case that is almost, but not quite, completely unlike database records. See
-[grammatical-parsers](https://github.com/blamario/grampa/tree/master/grammatical-parsers) about that.
+[grammatical-parsers](https://github.com/blamario/grampa/tree/master/grammatical-parsers) or
+[construct](https://hackage.haskell.org/package/construct) for examples.
diff --git a/rank2classes.cabal b/rank2classes.cabal
--- a/rank2classes.cabal
+++ b/rank2classes.cabal
@@ -1,5 +1,5 @@
 name:                rank2classes
-version:             1.3.2.1
+version:             1.4
 synopsis:            standard type constructor class hierarchy, only with methods of rank 2 types
 description:
   A mirror image of the standard type constructor class hierarchy rooted in 'Functor', except with methods of rank 2
@@ -38,7 +38,7 @@
   default-language:    Haskell2010
   -- other-modules:
   ghc-options:         -Wall
-  build-depends:       base >=4.12 && <5,
+  build-depends:       base >=4.10 && <5,
                        transformers >= 0.5 && < 0.6,
                        distributive < 0.7
 
diff --git a/src/Rank2.hs b/src/Rank2.hs
--- a/src/Rank2.hs
+++ b/src/Rank2.hs
@@ -6,7 +6,7 @@
 -- @Rank2.@ prefix and a twist that their methods operate on a heterogenous collection. The same property is shared by
 -- the two less standard classes 'Apply' and 'Distributive'.
 {-# LANGUAGE DefaultSignatures, InstanceSigs, KindSignatures, PolyKinds, Rank2Types #-}
-{-# LANGUAGE ScopedTypeVariables, TypeOperators #-}
+{-# LANGUAGE ScopedTypeVariables, StandaloneDeriving, TypeOperators, UndecidableInstances #-}
 {-# LANGUAGE EmptyCase #-}
 module Rank2 (
 -- * Rank 2 classes
@@ -24,10 +24,11 @@
 import qualified Control.Monad as Rank1
 import qualified Data.Foldable as Rank1
 import qualified Data.Traversable as Rank1
+import qualified Data.Functor.Compose as Rank1
+import qualified Data.Distributive as Rank1
 import Data.Coerce (coerce)
 import Data.Semigroup (Semigroup(..))
 import Data.Monoid (Monoid(..))
-import Data.Functor.Compose (Compose(Compose, getCompose))
 import Data.Functor.Const (Const(..))
 import Data.Functor.Product (Product(Pair))
 import Data.Functor.Sum (Sum(InL, InR))
@@ -50,6 +51,7 @@
 -- > (p . q) <$> g == p <$> (q <$> g)
 class Functor g where
    (<$>) :: (forall a. p a -> q a) -> g p -> g q
+infixl 4 <$>
 
 -- | Alphabetical synonym for '<$>'
 fmap :: Functor g => (forall a. p a -> q a) -> g p -> g q
@@ -64,9 +66,9 @@
 class (Functor g, Foldable g) => Traversable g where
    {-# MINIMAL traverse | sequence #-}
    traverse :: Rank1.Applicative m => (forall a. p a -> m (q a)) -> g p -> m (g q)
-   sequence :: Rank1.Applicative m => g (Compose m p) -> m (g p)
-   traverse f = sequence . fmap (Compose . f)
-   sequence = traverse getCompose
+   sequence :: Rank1.Applicative m => g (Rank1.Compose m p) -> m (g p)
+   traverse f = sequence . fmap (Rank1.Compose . f)
+   sequence = traverse Rank1.getCompose
 
 -- | Wrapper for functions that map the argument constructor type
 newtype Arrow p q a = Arrow{apply :: p a -> q a}
@@ -92,6 +94,7 @@
    (<*>) = liftA2 apply
    liftA2 f g h = (Arrow . f) <$> g <*> h
    liftA3 f g h i = liftA2 (\p q-> Arrow (f p q)) g h <*> i
+infixl 4 <*>
 
 liftA4 :: Apply g => (forall a. p a -> q a -> r a -> s a -> t a) -> g p -> g q -> g r -> g s -> g t
 liftA4 f g h i j = liftA3 (\p q r-> Arrow (f p q r)) g h i <*> j
@@ -110,23 +113,23 @@
 -- | Equivalent of 'Rank1.Distributive' for rank 2 data types
 class DistributiveTraversable g => Distributive g where
    {-# MINIMAL cotraverse|distribute #-}
-   collect :: Rank1.Functor f1 => (a -> g f2) -> f1 a -> g (Compose f1 f2)
-   distribute :: Rank1.Functor f1 => f1 (g f2) -> g (Compose f1 f2)
+   collect :: Rank1.Functor f1 => (a -> g f2) -> f1 a -> g (Rank1.Compose f1 f2)
+   distribute :: Rank1.Functor f1 => f1 (g f2) -> g (Rank1.Compose f1 f2)
    -- | Dual of 'traverse', equivalent of 'Rank1.cotraverse' for rank 2 data types 
    cotraverse :: Rank1.Functor m => (forall a. m (p a) -> q a) -> m (g p) -> g q
 
    collect f = distribute . Rank1.fmap f
-   distribute = cotraverse Compose
-   cotraverse f = (fmap (f . getCompose)) . distribute
+   distribute = cotraverse Rank1.Compose
+   cotraverse f = (fmap (f . Rank1.getCompose)) . distribute
 
 -- | A weaker 'Distributive' that requires 'Rank1.Traversable' to use, not just a 'Rank1.Functor'.
 class Functor g => DistributiveTraversable (g :: (k -> *) -> *) where
-   collectTraversable :: Rank1.Traversable f1 => (a -> g f2) -> f1 a -> g (Compose f1 f2)   
-   distributeTraversable :: Rank1.Traversable f1 => f1 (g f2) -> g (Compose f1 f2)
+   collectTraversable :: Rank1.Traversable f1 => (a -> g f2) -> f1 a -> g (Rank1.Compose f1 f2)   
+   distributeTraversable :: Rank1.Traversable f1 => f1 (g f2) -> g (Rank1.Compose f1 f2)
    cotraverseTraversable :: Rank1.Traversable f1 => (forall x. f1 (f2 x) -> f x) -> f1 (g f2) -> g f
 
    collectTraversable f = distributeTraversable . Rank1.fmap f
-   distributeTraversable = cotraverseTraversable Compose
+   distributeTraversable = cotraverseTraversable Rank1.Compose
    
    default cotraverseTraversable :: (Rank1.Traversable m, Distributive g) => 
                                     (forall a. m (p a) -> q a) -> m (g p) -> g q
@@ -138,23 +141,23 @@
 
 -- | Like 'fmap', but traverses over its argument
 fmapTraverse :: (DistributiveTraversable g, Rank1.Traversable f) => (forall a. f (t a) -> u a) -> f (g t) -> g u
-fmapTraverse f x = fmap (f . getCompose) (distributeTraversable x)
+fmapTraverse f x = fmap (f . Rank1.getCompose) (distributeTraversable x)
 
 -- | Like 'liftA2', but traverses over its first argument
 liftA2Traverse1 :: (Apply g, DistributiveTraversable g, Rank1.Traversable f) =>
                    (forall a. f (t a) -> u a -> v a) -> f (g t) -> g u -> g v
-liftA2Traverse1 f x = liftA2 (f . getCompose) (distributeTraversable x)
+liftA2Traverse1 f x = liftA2 (f . Rank1.getCompose) (distributeTraversable x)
 
 -- | Like 'liftA2', but traverses over its second argument
 liftA2Traverse2 :: (Apply g, DistributiveTraversable g, Rank1.Traversable f) => 
                    (forall a. t a -> f (u a) -> v a) -> g t -> f (g u) -> g v
-liftA2Traverse2 f x y = liftA2 (\x' y' -> f x' (getCompose y')) x (distributeTraversable y)
+liftA2Traverse2 f x y = liftA2 (\x' y' -> f x' (Rank1.getCompose y')) x (distributeTraversable y)
 
 -- | Like 'liftA2', but traverses over both its arguments
 liftA2TraverseBoth :: (Apply g, DistributiveTraversable g, Rank1.Traversable f1, Rank1.Traversable f2) =>
                       (forall a. f1 (t a) -> f2 (u a) -> v a) -> f1 (g t) -> f2 (g u) -> g v
 liftA2TraverseBoth f x y = liftA2 applyCompose (distributeTraversable x) (distributeTraversable y)
-   where applyCompose x' y' = f (getCompose x') (getCompose y')
+   where applyCompose x' y' = f (Rank1.getCompose x') (Rank1.getCompose y')
 
 {-# DEPRECATED distributeWith "Use cotraverse instead." #-}
 -- | Synonym for 'cotraverse'
@@ -167,7 +170,7 @@
                              (forall a. m (p a) -> q a) -> m (g p) -> g q
 distributeWithTraversable = cotraverseTraversable
 
--- | A rank-2 equivalent of '()', a zero-element tuple
+-- | A rank-2 equivalent of @()@, a zero-element tuple
 data Empty f = Empty deriving (Eq, Ord, Show)
 
 -- | A rank-2 tuple of only one element
@@ -176,6 +179,13 @@
 -- | Equivalent of 'Data.Functor.Identity' for rank 2 data types
 newtype Identity g f = Identity {runIdentity :: g f} deriving (Eq, Ord, Show)
 
+-- | Equivalent of 'Data.Functor.Compose' for rank 2 data types
+newtype Compose g p q = Compose {getCompose :: g (Rank1.Compose p q)}
+
+deriving instance Eq (g (Rank1.Compose p q)) => Eq (Compose g p q)
+deriving instance Ord (g (Rank1.Compose p q)) => Ord (Compose g p q)
+deriving instance Show (g (Rank1.Compose p q)) => Show (Compose g p q)
+
 -- | A nested parametric type represented as a rank-2 type
 newtype Flip g a f = Flip {unFlip :: g (f a)} deriving (Eq, Ord, Show)
 
@@ -216,6 +226,12 @@
 instance Functor g => Functor (Identity g) where
    f <$> Identity g = Identity (f <$> g)
 
+instance (Functor g, Rank1.Functor p) => Functor (Compose g p) where
+   (<$>) :: forall q r. (forall a. q a -> r a) -> Compose g p q -> Compose g p r
+   f <$> Compose g = Compose (f' <$> g)
+      where f' :: forall a. Rank1.Compose p q a -> Rank1.Compose p r a
+            f' (Rank1.Compose q) = Rank1.Compose (f Rank1.<$> q)
+
 instance (Functor g, Functor h) => Functor (Product g h) where
    f <$> Pair a b = Pair (f <$> a) (f <$> b)
 
@@ -246,7 +262,7 @@
    f <$> Generics.R1 x = Generics.R1 (f <$> x)
 
 instance (Functor f, Functor g) => Functor ((Generics.:*:) f g) where
-   f <$> (x Generics.:*: y) = f <$> x Generics.:*: f <$> y
+   f <$> (x Generics.:*: y) = (f <$> x) Generics.:*: (f <$> y)
 
 instance Foldable Empty where
    foldMap _ _ = mempty
@@ -263,6 +279,9 @@
 instance Foldable g => Foldable (Identity g) where
    foldMap f (Identity g) = foldMap f g
 
+instance (Foldable g, Rank1.Foldable p) => Foldable (Compose g p) where
+   foldMap f (Compose g) = foldMap (Rank1.foldMap f . Rank1.getCompose) g
+
 instance (Foldable g, Foldable h) => Foldable (Product g h) where
    foldMap f (Pair g h) = foldMap f g `mappend` foldMap f h
 
@@ -290,7 +309,7 @@
    foldMap f (Generics.R1 x) = foldMap f x
 
 instance (Foldable f, Foldable g) => Foldable ((Generics.:*:) f g) where
-   foldMap f (x Generics.:*: y) = foldMap f x <> foldMap f y
+   foldMap f (x Generics.:*: y) = foldMap f x `mappend` foldMap f y
 
 instance Traversable Empty where
    traverse _ _ = Rank1.pure Empty
@@ -307,6 +326,12 @@
 instance Traversable g => Traversable (Identity g) where
    traverse f (Identity g) = Identity Rank1.<$> traverse f g
 
+instance (Traversable g, Rank1.Traversable p) => Traversable (Compose g p) where
+   traverse :: forall m q r. Rank1.Applicative m => (forall a. q a -> m (r a)) -> Compose g p q -> m (Compose g p r)
+   traverse f (Compose g) = Compose Rank1.<$> traverse f' g
+      where f' :: forall a. Rank1.Compose p q a -> m (Rank1.Compose p r a)
+            f' (Rank1.Compose q) = Rank1.Compose Rank1.<$> Rank1.traverse f q
+
 instance (Traversable g, Traversable h) => Traversable (Product g h) where
    traverse f (Pair g h) = Rank1.liftA2 Pair (traverse f g) (traverse f h)
 
@@ -356,6 +381,16 @@
    Identity g <*> Identity h = Identity (g <*> h)
    liftA2 f (Identity g) (Identity h) = Identity (liftA2 f g h)
 
+instance (Apply g, Rank1.Applicative p) => Apply (Compose g p) where
+   (<*>)  :: forall q r. Compose g p (q ~> r) -> Compose g p q -> Compose g p r
+   liftA2 :: forall q r s. (forall a. q a -> r a -> s a) -> Compose g p q -> Compose g p r -> Compose g p s
+   Compose g <*> Compose h = Compose (liftA2 f' g h)
+      where f' :: forall a. Rank1.Compose p (q ~> r) a -> Rank1.Compose p q a -> Rank1.Compose p r a
+            f' (Rank1.Compose f) (Rank1.Compose q) = Rank1.Compose (Rank1.liftA2 apply f q)
+   liftA2 f (Compose g) (Compose h) = Compose (liftA2 f' g h)
+      where f' :: forall a. Rank1.Compose p q a -> Rank1.Compose p r a -> Rank1.Compose p s a
+            f' (Rank1.Compose q) (Rank1.Compose r) = Rank1.Compose (Rank1.liftA2 f q r)
+
 instance (Apply g, Apply h) => Apply (Product g h) where
    Pair gf hf <*> ~(Pair gx hx) = Pair (gf <*> gx) (hf <*> hx)
    liftA2 f (Pair g1 h1) ~(Pair g2 h2) = Pair (liftA2 f g1 g2) (liftA2 f h1 h2)
@@ -394,6 +429,9 @@
 instance Applicative g => Applicative (Identity g) where
    pure f = Identity (pure f)
 
+instance (Applicative g, Rank1.Applicative p) => Applicative (Compose g p) where
+   pure f = Compose (pure (Rank1.Compose (Rank1.pure f)))
+
 instance (Applicative g, Applicative h) => Applicative (Product g h) where
    pure f = Pair (pure f) (pure f)
 
@@ -414,6 +452,10 @@
 instance DistributiveTraversable (Only x)
 instance DistributiveTraversable g => DistributiveTraversable (Identity g) where
    cotraverseTraversable w f = Identity (cotraverseTraversable w (Rank1.fmap runIdentity f))
+instance (DistributiveTraversable g, Rank1.Distributive p) => DistributiveTraversable (Compose g p) where
+   cotraverseTraversable w f = Compose (cotraverseTraversable
+                                        (Rank1.Compose . Rank1.fmap w . Rank1.distribute . Rank1.fmap Rank1.getCompose)
+                                        (Rank1.fmap getCompose f))
 instance (DistributiveTraversable g, DistributiveTraversable h) => DistributiveTraversable (Product g h) where
    cotraverseTraversable w f = Pair (cotraverseTraversable w (Rank1.fmap fst f))
                                     (cotraverseTraversable w (Rank1.fmap snd f))
@@ -440,11 +482,15 @@
 instance Distributive g => Distributive (Identity g) where
    cotraverse w f = Identity (cotraverse w (Rank1.fmap runIdentity f))
 
+instance (Distributive g, Rank1.Distributive p) => Distributive (Compose g p) where
+   cotraverse w f = Compose (cotraverse (Rank1.Compose . Rank1.fmap w . Rank1.distribute . Rank1.fmap Rank1.getCompose)
+                                        (Rank1.fmap getCompose f))
+
 instance (Distributive g, Distributive h) => Distributive (Product g h) where
    cotraverse w f = Pair (cotraverse w (Rank1.fmap fst f)) (cotraverse w (Rank1.fmap snd f))
 
 instance Monoid c => DistributiveTraversable (Generics.K1 i c) where
-   cotraverseTraversable _ f = coerce (Rank1.fold f)
+   cotraverseTraversable _ f = coerce (Rank1.foldMap Generics.unK1 f)
 
 instance Distributive f => Distributive (Generics.M1 i c f) where
    cotraverse w f = Generics.M1 (cotraverse w (Rank1.fmap Generics.unM1 f))
diff --git a/src/Rank2/TH.hs b/src/Rank2/TH.hs
--- a/src/Rank2/TH.hs
+++ b/src/Rank2/TH.hs
@@ -46,6 +46,7 @@
               pragInlD '(Rank2.<*>) Inlinable FunLike AllPhases,
               pragInlD 'Rank2.liftA2 Inlinable FunLike AllPhases]]
 
+-- | This function always succeeds, but the methods it generates may be partial. Use with care.
 unsafeDeriveApply :: Name -> Q [Dec]
 unsafeDeriveApply ty = do
    (instanceType, cs) <- reifyConstructors ''Rank2.Apply ty
diff --git a/test/MyModule.lhs b/test/MyModule.lhs
--- a/test/MyModule.lhs
+++ b/test/MyModule.lhs
@@ -208,4 +208,5 @@
 ~~~
 
 Grammars are another use case that is almost, but not quite, completely unlike database records. See
-[grammatical-parsers](https://github.com/blamario/grampa/tree/master/grammatical-parsers) about that.
+[grammatical-parsers](https://github.com/blamario/grampa/tree/master/grammatical-parsers) or
+[construct](https://hackage.haskell.org/package/construct) for examples.
