diff --git a/CHANGES b/CHANGES
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,95 @@
+* 0.7.0.1: 29 January 2026
+
+  - Add some cautionary comments to `Regular` and `Semi`.  In
+    particular, `Regular` cannot be used to create a lawful semidirect
+    product of a monoid/semigroup with itself. ([#63](https://github.com/diagrams/monoid-extras/issues/63))
+
+* 0.7: 12 May 2025
+
+  - Updates to `Data.Monoid.Coproduct`:
+    - Fix `Eq` instance for monoid coproducts to take `mempty` into account
+    - `cop` implements coproduct universal map
+    - `untangleSemi`, like `untangle` but as a monoid homomorphism to semidirect product
+    - `toReducedAltList`, like `toAltList` but also gets rid of `mempty`
+  - New module `Data.Semigroup.Coproduct` with semigroup coproducts
+  - Remove `Data.Monoid.Coproduct.Strict`
+
+  Thanks to Sonat Süer (@sonatsuer) for the updates!
+
+* 0.6.5: 22 February 2025
+
+- New instance `Eq (m :+: n)` ([#59](https://github.com/diagrams/monoid-extras/issues/59))
+- New function `toAltList :: (m :+: n) -> [Either m n]`
+
+* 0.6.4: 10 February 2025
+
+- New instance `Action m a => Action [m] a` (thanks to Manuel Bärenz for the suggestion)
+
+* 0.6.3: 8 August 2024
+
+- New instances (thanks to Clinton Mead):
+  - `Action (First a) a`
+  - `Action Void a`
+  - `Action m a => Action (Identity m) a`
+
+- r1 (27 Jan 2025):
+  - Allow `base-4.21` and test on GHC 9.12.
+
+* 0.6.2: 20 Dec 2022
+
+- New class and newtypes in `Data.Monoid.Action` (thanks to Manuel Bärenz):
+    - `Torsor` class for transitive group actions
+    - `Regular` and `Conjugate` newtypes for groups acting on themselves
+
+- r1 (27 March 2023):
+  - allow `semigroupoids-6.0`
+  - allow `base-4.18` and test on GHC 9.6.
+
+- r2 (17 Oct 2023):
+  - allow `base-4.19` and test on GHC 9.8.
+
+- r3 (15 May 2024):
+  - allow `base-4.20` and test on GHC 9.10.
+
+* 0.6.1: 16 Nov 2021
+
+- Add more efficient `stimes` implementations for several `Semigroup`
+  instances.  Thanks to BlackCapCoder for the patch!
+- Allow `base-4.16` and test on GHC 9.2.1.
+
+- r1: allow `base-4.16` in benchmarks
+- r2 (15 August 2022): allow `base-4.17` and test with GHC 9.4.
+
+* 0.6: 8 May 2021
+
+- Updates for GHC 8.10 and 9.0.
+- Drop support for GHC 8.2 or older.
+- Replace deprecated `Option` type with `Maybe`.
+
+* 0.5.1: 19 Oct 2019
+
+- New module Data.Monoid.Coproduct.Strict for a more efficient coproduct in
+  some use cases.
+- Update for GHC 8.8.
+- Drop support for GHC 7.8.
+
+* 0.5: 14 May 2018
+
+- Modernize Data.Monoid.WithSemigroup
+
+  It used to export a type class Monoid' with no methods and a single
+  instance, for use as a "poor man's constraint synonym" for the
+  combination of Monoid and Semigroup.  Now Monoid':
+
+    - Is a real constraint synonym, using ConstraintKinds.
+    - Is simply a synonym for Monoid under base-4.11 and later, in
+      which case Semigroup is already a superclass of Monoid.
+
+  This technically necessitates a major version bump but should not
+  cause any issues for packages that depend on monoid-extras, other
+  than potentially requiring the addition of a ConstraintKinds pragma
+  under GHC 7.8.
+
 * 0.4.4: 8 April 2018
 
 - Fix build on older (< 7.10) GHCs (thanks to George Wilson for the fix)
diff --git a/monoid-extras.cabal b/monoid-extras.cabal
--- a/monoid-extras.cabal
+++ b/monoid-extras.cabal
@@ -1,5 +1,5 @@
 name:                monoid-extras
-version:             0.4.4
+version:             0.7.0.2
 synopsis:            Various extra monoid-related definitions and utilities
 description:         Various extra monoid-related definitions and utilities,
                      such as monoid actions, monoid coproducts, semi-direct
@@ -14,7 +14,7 @@
 category:            Data
 build-type:          Simple
 cabal-version:       >=1.10
-tested-with:         GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.2, GHC == 8.0.1, GHC == 8.2.2, GHC == 8.4.1
+tested-with:         GHC ==8.4.4 || ==8.6.5 || ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.8 || ==9.4.8 || ==9.6.6 || ==9.8.2 || ==9.10.1 || ==9.12.1 || ==9.14.1
 
 source-repository head
   type: git
@@ -33,25 +33,28 @@
                      Data.Monoid.MList,
                      Data.Monoid.Recommend,
                      Data.Monoid.Split,
-                     Data.Monoid.WithSemigroup
+                     Data.Monoid.WithSemigroup,
+                     Data.Semigroup.Coproduct
 
-  build-depends:     base >= 4.3 && < 4.12,
-                     groups < 0.5,
-                     semigroups >= 0.8 && < 0.19,
-                     semigroupoids >= 4.0 && < 5.3
+  build-depends:     base >= 4.11 && < 4.23,
+                     groups < 0.6,
+                     semigroupoids >= 4.0 && < 6.1
 
   hs-source-dirs:    src
 
-  other-extensions:  DeriveFunctor,
-                     FlexibleInstances,
-                     MultiParamTypeClasses,
+  ghc-options: -Wall
+
+  other-extensions:  DeriveFunctor
+                     FlexibleInstances
+                     MultiParamTypeClasses
                      TypeOperators
+                     ConstraintKinds
 
 benchmark semi-direct-product
   default-language:  Haskell2010
   hs-source-dirs: benchmarks
   main-is: SemiDirectProduct.hs
   type: exitcode-stdio-1.0
-  build-depends: base          >= 4.3 &&  < 4.12
+  build-depends: base          >= 4.3 &&  < 4.23
                , criterion
                , monoid-extras
diff --git a/src/Data/Monoid/Action.hs b/src/Data/Monoid/Action.hs
--- a/src/Data/Monoid/Action.hs
+++ b/src/Data/Monoid/Action.hs
@@ -14,9 +14,17 @@
 
 module Data.Monoid.Action
        ( Action(..)
+       , Regular(..)
+       , Conjugate(..)
+       , Torsor(..)
        ) where
 
+import           Data.Functor.Identity (Identity(Identity))
 import           Data.Semigroup
+import qualified Data.Semigroup as Semigroup
+import           Data.Group
+import qualified Data.Monoid as Monoid
+import           Data.Void (Void, absurd)
 
 ------------------------------------------------------------
 --  Monoid and semigroup actions
@@ -31,12 +39,16 @@
 --   * @act (m1 \`mappend\` m2) = act m1 . act m2@
 --
 --   Semigroup instances are required to satisfy the second law but with
---   ('<>') instead of 'mappend'.  Additionally, if the type @s@ has
---   any algebraic structure, @act m@ should be a homomorphism.  For
---   example, if @s@ is also a monoid we should have @act m mempty =
---   mempty@ and @act m (s1 \`mappend\` s2) = (act m s1) \`mappend\`
---   (act m s2)@.
+--   ('<>') instead of 'mappend'.
 --
+--   Additionally, if the type @s@ has any algebraic structure, @act
+--   m@ should typically be a homomorphism.  For example, if @s@ is
+--   also a monoid we should have @act m mempty = mempty@ and @act m
+--   (s1 \`mappend\` s2) = (act m s1) \`mappend\` (act m s2)@.  In
+--   particular, these laws are necessary for the semidirect product
+--   @Semi s m@ to be a valid semigroup/monoid.  For a more
+--   fine-grained treatment of these ideas, see the @lr-acts@ package.
+--
 --   By default, @act = const id@, so for a type @M@ which should have
 --   no action on anything, it suffices to write
 --
@@ -64,10 +76,14 @@
   act () = id
 
 -- | @Nothing@ acts as the identity; @Just m@ acts as @m@.
-instance Action m s => Action (Option m) s where
-  act (Option Nothing)  s = s
-  act (Option (Just m)) s = act m s
+instance Action m s => Action (Maybe m) s where
+  act Nothing  s = s
+  act (Just m) s = act m s
 
+-- | @act [a,b,c,...] = act a . act b . act c . ...@
+instance Action m s => Action [m] s where
+  act = flip (foldr act)
+
 -- | @Endo@ acts by application.
 --
 --   Note that in order for this instance to satisfy the @Action@
@@ -81,3 +97,64 @@
 instance Action (Endo a) a where
   act = appEndo
 
+instance Num a => Action Integer (Sum a) where
+  n `act` a = fromInteger n <> a
+
+instance Num a => Action Integer (Product a) where
+  n `act` a = fromInteger n <> a
+
+instance Fractional a => Action Rational (Sum a) where
+  n `act` a = Sum (fromRational n) <> a
+
+instance Fractional a => Action Rational (Product a) where
+  n `act` a = Product (fromRational n) <> a
+
+-- | An action of a group is "free transitive", "regular", or a "torsor"
+--   iff it is invertible.
+--
+--   Given an original value `sOrig`, and a value `sActed` that is the result
+--   of acting on `sOrig` by some `m`,
+--   it is possible to recover this `m`.
+--   This is encoded in the laws:
+--
+--   * @(m `'act'` s) `'difference'` s = m@
+--   * @(sActed `'difference'` sOrig) `'act'` sOrig = sActed@
+class Group m => Torsor m s where
+
+  -- | @'difference' sActed sOrig@ is the element @m@ such that @sActed = m `'act'` sOrig@.
+  difference :: s -> s -> m
+
+-- | Any monoid acts on itself by left multiplication.
+--   This newtype witnesses this action:
+--   @'getRegular' $ 'Regular' m1 `'act'` 'Regular' m2 = m1 '<>' m2@
+--
+--   Note that this typically does NOT satisfy the distributivity law
+--   @m `act` (m1 <> m2) = (m `act` m1) <> (m `act` m2)@, and hence
+--   cannot be used to form a lawful semidirect product of a monoid with itself.
+newtype Regular m = Regular {getRegular :: m}
+
+instance Semigroup m => Action m (Regular m) where
+  m1 `act` Regular m2 = Regular $ m1 <> m2
+
+instance Group m => Torsor m (Regular m) where
+  Regular m1 `difference` Regular m2 = m1 ~~ m2
+
+-- | Any group acts on itself by conjugation.
+newtype Conjugate m = Conjugate { getConjugate :: m }
+
+instance Group m => Action m (Conjugate m) where
+  m1 `act` Conjugate m2 = Conjugate $ m1 <> m2 ~~ m1
+
+instance Action (Semigroup.First a) a where
+  act (Semigroup.First m) _ = m
+
+instance Action (Monoid.First a) a where 
+  act (Monoid.First m) s = case m of
+    Nothing -> s
+    Just m' -> m'
+
+instance Action Void a where
+  act = absurd
+
+instance Action m s => Action (Identity m) s where
+  act (Identity m) = act m
diff --git a/src/Data/Monoid/Coproduct.hs b/src/Data/Monoid/Coproduct.hs
--- a/src/Data/Monoid/Coproduct.hs
+++ b/src/Data/Monoid/Coproduct.hs
@@ -2,6 +2,7 @@
 {-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeOperators         #-}
+{-# LANGUAGE LambdaCase #-}
 
 -----------------------------------------------------------------------------
 -- |
@@ -18,30 +19,79 @@
        ( (:+:)
        , inL, inR
        , mappendL, mappendR
+       , cop
        , killL, killR
+       , toAltList
+       , toReducedAltList
        , untangle
-
+       , untangleSemi
        ) where
 
-import Data.Either        (lefts, rights)
+import Data.Function      (on)
 import Data.Semigroup
 import Data.Typeable
 
 import Data.Monoid.Action
+import Data.Monoid.SemiDirectProduct ( embed, inject, Semi, unSemi )
+import Data.Tuple (swap)
 
 -- | @m :+: n@ is the coproduct of monoids @m@ and @n@.  Values of
 --   type @m :+: n@ consist of alternating lists of @m@ and @n@
---   values.  The empty list is the identity, and composition is list
+--   values. The empty list is the identity, and composition is list
 --   concatenation, with appropriate combining of adjacent elements
---   when possible.
+--   and removing identities when possible.
 newtype m :+: n = MCo { unMCo :: [Either m n] }
   deriving (Typeable, Show)
 
+instance (Eq m, Eq n, Monoid m, Monoid n) => Eq (m :+: n) where
+  (==) = (==) `on` (normalizeEq . unMCo)
+
+-- | Extract a monoid coproduct to a list of @Either@ values.  The
+--   resulting list is guaranteed to be normalized, in the sense that
+--   it will strictly alternate between @Left@ and @Right@.
+toAltList :: (Semigroup m, Semigroup n) => (m :+: n) -> [Either m n]
+toAltList (MCo ms) = normalize ms
+
+-- | Extract a monoid coproduct to a list of @Either@ values.  The
+--   resulting list is guaranteed to be normalized, in the sense that
+--   it will strictly alternate between @Left@ and @Right@ and no identity
+--   element from @m@ or @n@ will occur in the list.
+toReducedAltList :: (Eq m, Eq n, Monoid m, Monoid n) => (m :+: n) -> [Either m n]
+toReducedAltList (MCo ms) = normalizeEq ms
+
+-- Normalize a list of @Either@ values by combining any consecutive
+-- values of the same type.
+normalize :: (Semigroup m, Semigroup n) => [Either m n] -> [Either m n]
+normalize = \case
+  (Left e1:Left e2 : es) -> normalize (Left (e1 <> e2) : es)
+  (Right e1:Right e2:es) -> normalize (Right (e1 <> e2) : es)
+  []  -> []
+  (e:es) -> e : normalize es
+
+
+-- Similar to @normalize@. In addition to combining consecutive values of the same
+-- type it also removes the identities.
+normalizeEq :: (Eq m, Eq n, Monoid m, Monoid n) => [Either m n] -> [Either m n]
+normalizeEq es = until (all nonIdentity) reduce (normalize es)
+  where
+    reduce = normalize . filter nonIdentity
+    nonIdentity e = e /= Left mempty && e /= Right mempty
+
 -- For efficiency and simplicity, we implement it just as [Either m
 -- n]: of course, this does not preserve the invariant of strictly
 -- alternating types, but it doesn't really matter as long as we don't
 -- let anyone inspect the internal representation.
 
+-- | Universal map of the coproduct. The name @cop@ is an abbreviation
+--   for copairing. Both functions in the signature should be monoid
+--   homomorphisms. If they are general functions then the copairing may
+--   not be well defined in the sense that it may send equal elements to
+--   unequal elements. This is also the reason why @cop@ is not the
+--   @Data.Bifoldable.bifoldMap@ function even though they have the same
+--   signature.
+cop :: Monoid k => (m -> k) -> (n -> k) -> (m :+: n) -> k
+f `cop` g = foldMap (either f g) . unMCo
+
 -- | Injection from the left monoid into a coproduct.
 inL :: m -> m :+: n
 inL m = MCo [Left m]
@@ -58,17 +108,6 @@
 mappendR :: n -> m :+: n -> m :+: n
 mappendR = mappend . inR
 
-{-
-normalize :: (Monoid m, Monoid n) => m :+: n -> m :+: n
-normalize (MCo es) = MCo (normalize' es)
-  where normalize' []  = []
-        normalize' [e] = [e]
-        normalize' (Left e1:Left e2 : es) = normalize' (Left (e1 <> e2) : es)
-        normalize' (Left e1:es) = Left e1 : normalize' es
-        normalize' (Right e1:Right e2:es) = normalize' (Right (e1 <> e2) : es)
-        normalize' (Right e1:es) = Right e1 : normalize' es
--}
-
 instance Semigroup (m :+: n) where
   (MCo es1) <> (MCo es2) = MCo (es1 ++ es2)
 
@@ -80,14 +119,21 @@
 -- | @killR@ takes a value in a coproduct monoid and sends all the
 --   values from the right monoid to the identity.
 killR :: Monoid m => m :+: n -> m
-killR = mconcat . lefts . unMCo
+killR = id `cop` const mempty
 
 -- | @killL@ takes a value in a coproduct monoid and sends all the
 --   values from the left monoid to the identity.
 killL :: Monoid n => m :+: n -> n
-killL = mconcat . rights . unMCo
+killL = const mempty `cop` id
 
--- | Take a value from a coproduct monoid where the left monoid has an
+-- | The copairing of @embed@ and @inject@ homomorphisms into the
+--   semidirect product. Note that @embed@ and @inject@ are monoid
+--   homomorphisms. Therefore @untangleSemi@ is also a monoid homomorphism.
+untangleSemi :: (Action m n, Monoid m, Monoid n) => m :+: n -> Semi n m
+untangleSemi = embed `cop` inject
+
+-- | Same as @untangleSemi@ but the result is uwrapped. Concretely, given
+--   a value from a coproduct monoid where the left monoid has an
 --   action on the right, and \"untangle\" it into a pair of values.  In
 --   particular,
 --
@@ -100,12 +146,9 @@
 --   That is, before combining @n@ values, every @n@ value is acted on
 --   by all the @m@ values to its left.
 untangle :: (Action m n, Monoid m, Monoid n) => m :+: n -> (m,n)
-untangle (MCo elts) = untangle' mempty elts
-  where untangle' cur [] = cur
-        untangle' (curM, curN) (Left m : elts')  = untangle' (curM `mappend` m, curN) elts'
-        untangle' (curM, curN) (Right n : elts') = untangle' (curM, curN `mappend` act curM n) elts'
+untangle = swap . unSemi . untangleSemi
 
 -- | Coproducts act on other things by having each of the components
 --   act individually.
 instance (Action m r, Action n r) => Action (m :+: n) r where
-  act = appEndo . mconcat . map (Endo . either act act) . unMCo
+  act = appEndo . ((Endo . act) `cop` (Endo . act))
diff --git a/src/Data/Monoid/Cut.hs b/src/Data/Monoid/Cut.hs
--- a/src/Data/Monoid/Cut.hs
+++ b/src/Data/Monoid/Cut.hs
@@ -61,6 +61,9 @@
   (m1  :||: m2) <> (Uncut m2')   = m1        :||: m2 <> m2'
   (m11 :||: _)  <> (_ :||: m22)  = m11       :||: m22
 
+  stimes n (Uncut m) = Uncut (stimes n m)
+  stimes _ (m      ) = m
+
 instance (Semigroup m, Monoid m) => Monoid (Cut m) where
   mempty  = Uncut mempty
   mappend = (<>)
diff --git a/src/Data/Monoid/Deletable.hs b/src/Data/Monoid/Deletable.hs
--- a/src/Data/Monoid/Deletable.hs
+++ b/src/Data/Monoid/Deletable.hs
@@ -77,6 +77,13 @@
     | l1 <  r2  = Deletable (r1 + r2 - l1) m2 l2
     | otherwise = Deletable r1 m1 (l2 + l1 - r2)
 
+  stimes n (Deletable r m l)
+    | r == l    = Deletable r (stimes n m) l
+    | l <  r    = Deletable (i*(r-l) + l) m l
+    | otherwise = Deletable r m (i*(l-r) + r)
+    where
+      i = fromIntegral n :: Int
+
 instance (Semigroup m, Monoid m) => Monoid (Deletable m) where
   mempty = Deletable 0 mempty 0
   mappend = (<>)
diff --git a/src/Data/Monoid/Inf.hs b/src/Data/Monoid/Inf.hs
--- a/src/Data/Monoid/Inf.hs
+++ b/src/Data/Monoid/Inf.hs
@@ -16,8 +16,8 @@
 --
 -- Make semigroups under 'min' or 'max' into monoids by adjoining an
 -- element corresponding to infinity (positive or negative,
--- respectively). These types are similar to @Option (Min a)@ and
--- @Option (Max a)@ respectively, except that the 'Ord' instance
+-- respectively). These types are similar to @Maybe (Min a)@ and
+-- @Maybe (Max a)@ respectively, except that the 'Ord' instance
 -- matches the 'Monoid' instance.
 --
 -----------------------------------------------------------------------------
diff --git a/src/Data/Monoid/MList.hs b/src/Data/Monoid/MList.hs
--- a/src/Data/Monoid/MList.hs
+++ b/src/Data/Monoid/MList.hs
@@ -44,7 +44,6 @@
 
 import           Control.Arrow
 import           Data.Monoid.Action
-import           Data.Semigroup
 
 -- $mlist
 --
@@ -58,10 +57,10 @@
 infixr 5 :::
 infixr 5 *:
 
-type a ::: l = (Option a, l)
+type a ::: l = (Maybe a, l)
 
 (*:) :: a -> l -> a ::: l
-a *: l = (Option (Just a), l)
+a *: l = (Just a, l)
 
 -- MList -----------------------------------
 
@@ -77,7 +76,7 @@
   empty     = ()
 
 instance MList l => MList (a ::: l) where
-  empty   = (Option Nothing, empty)
+  empty   = (Nothing, empty)
 
 -- Embedding -------------------------------------------
 
@@ -89,22 +88,22 @@
 
   -- | Get the value of type @a@ from a heterogeneous list, if there
   --   is one.
-  get  :: l -> Option a
+  get  :: l -> Maybe a
 
   -- | Alter the value of type @a@ by applying the given function to it.
-  alt  :: (Option a -> Option a) -> l -> l
+  alt  :: (Maybe a -> Maybe a) -> l -> l
 
 #if __GLASGOW_HASKELL__ >= 710
 instance {-# OVERLAPPING #-} MList t => (:>:) (a ::: t) a where
 #else
 instance MList t => (:>:) (a ::: t) a where
 #endif
-  inj a = (Option (Just a), empty)
+  inj a = (Just a, empty)
   get   = fst
   alt   = first
 
 instance (t :>: a) => (:>:) (b ::: t) a where
-  inj a = (Option Nothing, inj a)
+  inj a = (Nothing, inj a)
   get   = get . snd
   alt   = second . alt
 
@@ -129,6 +128,6 @@
 instance Action (SM a) () where
   act _ _ = ()
 
-instance (Action a a', Action (SM a) l) => Action (SM a) (Option a', l) where
-  act (SM a) (Option Nothing,   l) = (Option Nothing, act (SM a) l)
-  act (SM a) (Option (Just a'), l) = (Option (Just (act a a')), act (SM a) l)
+instance (Action a a', Action (SM a) l) => Action (SM a) (Maybe a', l) where
+  act (SM a) (Nothing,   l) = (Nothing, act (SM a) l)
+  act (SM a) (Just a', l) = (Just (act a a'), act (SM a) l)
diff --git a/src/Data/Monoid/Recommend.hs b/src/Data/Monoid/Recommend.hs
--- a/src/Data/Monoid/Recommend.hs
+++ b/src/Data/Monoid/Recommend.hs
@@ -3,6 +3,8 @@
 {-# LANGUAGE DeriveFoldable     #-}
 {-# LANGUAGE DeriveFunctor      #-}
 {-# LANGUAGE DeriveTraversable  #-}
+
+{-# OPTIONS_GHC -fno-warn-unused-imports #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Data.Monoid.Recommend
@@ -12,7 +14,7 @@
 --
 -- A type for representing values with an additional bit saying
 -- whether the value is \"just a recommendation\" (to be used only if
--- nothing better comes along) or a \"committment\" (to certainly be
+-- nothing better comes along) or a \"commitment\" (to certainly be
 -- used, overriding merely recommended values), along with
 -- corresponding @Semigroup@ and @Monoid@ instances.
 --
@@ -55,6 +57,9 @@
   Recommend _ <> Commit b    = Commit b
   Commit a    <> Recommend _ = Commit a
   Commit a    <> Commit b    = Commit (a <> b)
+
+  stimes n (Recommend m) = Recommend (stimes n m)
+  stimes n (Commit    m) = Commit    (stimes n m)
 
 instance (Semigroup a, Monoid a) => Monoid (Recommend a) where
   mappend = (<>)
diff --git a/src/Data/Monoid/SemiDirectProduct.hs b/src/Data/Monoid/SemiDirectProduct.hs
--- a/src/Data/Monoid/SemiDirectProduct.hs
+++ b/src/Data/Monoid/SemiDirectProduct.hs
@@ -24,6 +24,18 @@
 --   We think of the @m@ values as a "tag" decorating the @s@ values,
 --   which also affect the way the @s@ values combine.
 --
+--   NOTE: this is only a valid semigroup/monoid if the action of @m@
+--   on @s@ satisfies BOTH:
+--
+--     1. @act@ is a monoid/semigroup homomorphism from @m@ to @(s ->
+--       s)@, that is, @act mempty = id@ and @act (m1 <> m2) = act m1
+--       . act m2@
+--     2. @act m@ is a monoid/semigroup homomorphism for any @m@, that is,
+--       @act m mempty = mempty@ and @act m (s1 <> s2) = act m s1 <> act m s2@.
+--
+--   For a more fine-grained treatment of these ideas, see the
+--   @lr-acts@ package.
+--
 --   We call the monoid @m@ the quotient monoid and the monoid @s@ the
 --   sub-monoid of the semi-direct product. The semi-direct product
 --   @Semi s m@ is an extension of the monoid @s@ with @m@ being the
diff --git a/src/Data/Monoid/Split.hs b/src/Data/Monoid/Split.hs
--- a/src/Data/Monoid/Split.hs
+++ b/src/Data/Monoid/Split.hs
@@ -67,6 +67,10 @@
   (m1  :| m2)  <> (M m2')      = m1                :| m2 <> m2'
   (m11 :| m12) <> (m21 :| m22) = m11 <> m12 <> m21 :| m22
 
+  stimes n (M m     ) = M (stimes n m)
+  stimes 1 (m       ) = m
+  stimes n (m1 :| m2) = m1 <> stimes (pred n) (m2 <> m1) :| m2
+
 instance (Semigroup m, Monoid m) => Monoid (Split m) where
   mempty  = M mempty
   mappend = (<>)
diff --git a/src/Data/Monoid/WithSemigroup.hs b/src/Data/Monoid/WithSemigroup.hs
--- a/src/Data/Monoid/WithSemigroup.hs
+++ b/src/Data/Monoid/WithSemigroup.hs
@@ -1,7 +1,9 @@
-{-# LANGUAGE FlexibleInstances
-           , UndecidableInstances
-  #-}
+{-# LANGUAGE ConstraintKinds      #-}
+{-# LANGUAGE CPP                  #-}
+{-# LANGUAGE FlexibleInstances    #-}
+{-# LANGUAGE UndecidableInstances #-}
 
+{-# OPTIONS_GHC -fno-warn-unused-imports #-}
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Data.Monoid.WithSemigroup
@@ -17,16 +19,15 @@
        ( Monoid'
        ) where
 
-import Data.Semigroup
-
--- Poor man's constraint synonym.  Eventually, once it becomes
--- standard, we can make this a real constraint synonym and get rid of
--- the UndecidableInstances flag.  Better yet, hopefully the Monoid
--- class will eventually have a Semigroup superclass.
+import           Data.Semigroup
 
--- | The @Monoid'@ class is a synonym for things which are instances
---   of both 'Semigroup' and 'Monoid'.  Ideally, the 'Monoid' class
---   itself will eventually include a 'Semigroup' superclass and we
---   can get rid of this.
-class (Semigroup m, Monoid m) => Monoid' m
-instance (Semigroup m, Monoid m) => Monoid' m
+-- | For base < 4.11, the @Monoid'@ constraint is a synonym for things
+--   which are instances of both 'Semigroup' and 'Monoid'.  For base
+--   version 4.11 and onwards, @Monoid@ has @Semigroup@ as a
+--   superclass already, so for backwards compatibility @Monoid'@ is
+--   provided as a synonym for @Monoid@.
+#if MIN_VERSION_base(4,11,0)
+type Monoid' = Monoid
+#else
+type Monoid' m = (Semigroup m, Monoid m)
+#endif
diff --git a/src/Data/Semigroup/Coproduct.hs b/src/Data/Semigroup/Coproduct.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Semigroup/Coproduct.hs
@@ -0,0 +1,85 @@
+{-# LANGUAGE DeriveDataTypeable    #-}
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeOperators         #-}
+{-# LANGUAGE LambdaCase #-}
+
+module Data.Semigroup.Coproduct
+       ( (:+.)
+       , inL, inR
+       , cop
+       , toAltList
+       , toMonoid
+       ) where
+
+import Data.Function (on)
+import Data.List.NonEmpty (NonEmpty(..))
+import Data.Typeable (Typeable)
+import Data.Semigroup (Endo(Endo, appEndo))
+import Data.Semigroup.Foldable (foldMap1)
+
+import Data.Monoid.Action (Action(..))
+import Data.Monoid.Coproduct ((:+:))
+import qualified Data.Monoid.Coproduct as M
+
+-- | @m :+. n@ is the coproduct of semigroups @m@ and @n@.  Values of
+--   type @m :+. n@ consist of alternating non-empty lists of @m@ and @n@
+--   values. Composition is list concatenation, with appropriate
+--   combining of adjacent elements
+newtype m :+. n = SCo { unSCo :: NonEmpty (Either m n) }
+  deriving (Typeable, Show)
+
+instance (Eq m, Eq n, Semigroup m, Semigroup n) => Eq (m :+. n) where
+  (==) = (==) `on` (normalize . unSCo)
+
+-- | Extract a semigroup coproduct to a non-empty list of @Either@ values.
+--   The resulting list is guaranteed to be normalized, in the sense that
+--   it will strictly alternate between @Left@ and @Right@.
+toAltList :: (Semigroup m, Semigroup n) => (m :+. n) -> NonEmpty (Either m n)
+toAltList (SCo ms) = normalize ms
+
+-- Normalize a list of @Either@ values by combining any consecutive
+-- values of the same type.
+normalize :: (Semigroup m, Semigroup n) => NonEmpty (Either m n) -> NonEmpty (Either m n)
+normalize = \case
+  Left e1 :| Left e2 : es -> normalize (Left (e1 <> e2) :| es)
+  Right e1 :| Right e2 : es -> normalize (Right (e1 <> e2) :| es)
+  e1 :| es1 -> case es1 of
+    e2 : es2 -> (e1 :| []) <> normalize (e2 :| es2)
+    [] -> e1 :| []
+
+-- | Universal map of the coproduct. The name @cop@ is an abbreviation
+--   for copairing. Both functions in the signature should be semigroup
+--   homomorphisms. If they are general functions then the copairing may
+--   not be well defined in the sense that it may send equal elements to
+--   unequal elements. This is also the reason why @cop@ is not the
+--   @Data.Bifoldable1.bifoldMap1@ function even though they have the same
+--   signature.
+cop :: Semigroup k => (m -> k) -> (n -> k) -> (m :+. n) -> k
+f `cop` g = foldMap1 (either f g) . unSCo
+
+-- | Injection from the left semigroup into a coproduct.
+inL :: m -> m :+. n
+inL m = SCo (Left m :| [])
+
+-- | Injection from the right semigroup into a coproduct.
+inR :: n -> m :+. n
+inR n = SCo (Right n :| [])
+
+-- | Given monoids @m@ and @n@, we can form their semigroup coproduct
+--   @m :+. n@. Every monoid homomorphism is a semigroup homomorphism.
+--   In particular the canonical inections of the monoid coproduct from
+--   @m@ and @n@ into @m :+: n@ are semigroup homomorphisms. By pairing
+--   them using the universal property of the semigroup coproduct we
+--   obtain a canonical semigroup homomorphism `toMonoid` from @m :+. n@
+--   to @m :+: n@.
+toMonoid :: (Monoid m, Monoid n) => m :+. n -> m :+: n
+toMonoid = M.inL `cop` M.inR
+
+instance Semigroup (m :+. n) where
+  (SCo es1) <> (SCo es2) = SCo (es1 <> es2)
+
+-- | Coproducts act on other things by having each of the components
+--   act individually.
+instance (Action m r, Action n r) => Action (m :+. n) r where
+  act = appEndo . ((Endo . act) `cop` (Endo . act))
