diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown
--- a/CHANGELOG.markdown
+++ b/CHANGELOG.markdown
@@ -1,3 +1,11 @@
+0.17
+----
+* Added `groupWith`, `groupAllWith`, `groupWith1`, `groupAllWith1`
+* Renamed `sortOn` to `sortWith` to match the "Comprehensive comprehensions" paper and `TransformListComp` extension.
+* Add `Semigroup` instances for `Alt`, `Void`, `Proxy` and `Tagged`
+* Add `Num` instances for `Min` and `Max`
+* Removed `times1p` in favor of `stimes`.
+
 0.16.2.2
 --------
 * Cleaned up imports to remove warnings on GHC 7.10.
diff --git a/README.markdown b/README.markdown
--- a/README.markdown
+++ b/README.markdown
@@ -1,7 +1,7 @@
 semigroups
 ==========
 
-[![Build Status](https://secure.travis-ci.org/ekmett/semigroups.png?branch=master)](http://travis-ci.org/ekmett/semigroups)
+[![Hackage](https://img.shields.io/hackage/v/semigroups.svg)](https://hackage.haskell.org/package/semigroups) [![Build Status](https://secure.travis-ci.org/ekmett/semigroups.png?branch=master)](http://travis-ci.org/ekmett/semigroups)
 
 Haskellers are usually familiar with monoids. A monoid has an appending operation `<>` or `mappend` and an identity element `mempty`. A Semigroup has an append `<>`, but does not require an `mempty` element. A Monoid can be made a Semigroup with just `instance Semigroup MyMonoid`
 
diff --git a/semigroups.cabal b/semigroups.cabal
--- a/semigroups.cabal
+++ b/semigroups.cabal
@@ -1,6 +1,6 @@
 name:          semigroups
 category:      Algebra, Data, Data Structures, Math
-version:       0.16.2.2
+version:       0.17
 license:       BSD3
 cabal-version: >= 1.10
 license-file:  LICENSE
@@ -56,6 +56,14 @@
   default: True
   manual: True
 
+flag tagged
+  description:
+    You can disable the use of the `tagged` package using `-f-tagged`.
+    .
+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.
+  default: True
+  manual: True
+
 flag text
   description:
     You can disable the use of the `text` package using `-f-text`.
@@ -100,6 +108,9 @@
 
   if flag(deepseq)
     build-depends: deepseq >= 1.1 && < 1.5
+
+  if flag(tagged)
+    build-depends: tagged >= 0.4.4 && < 1
 
   if flag(text)
     build-depends: text >= 0.10 && < 2
diff --git a/src/Data/List/NonEmpty.hs b/src/Data/List/NonEmpty.hs
--- a/src/Data/List/NonEmpty.hs
+++ b/src/Data/List/NonEmpty.hs
@@ -52,7 +52,7 @@
    , scanr1      -- :: (a -> a -> a) -> NonEmpty a -> NonEmpty a
    , transpose   -- :: NonEmpty (NonEmpty a) -> NonEmpty (NonEmpty a)
    , sortBy      -- :: (a -> a -> Ordering) -> NonEmpty a -> NonEmpty a
-   , sortOn      -- :: Ord o => (a -> o) -> NonEmpty a -> NonEmpty a
+   , sortWith      -- :: Ord o => (a -> o) -> NonEmpty a -> NonEmpty a
    -- * Basic functions
    , length      -- :: NonEmpty a -> Int
    , head        -- :: NonEmpty a -> a
@@ -85,8 +85,12 @@
    , partition   -- :: (a -> Bool) -> NonEmpty a -> ([a],[a])
    , group       -- :: Foldable f => Eq a => f a -> [NonEmpty a]
    , groupBy     -- :: Foldable f => (a -> a -> Bool) -> f a -> [NonEmpty a]
+   , groupWith     -- :: (Foldable f, Eq b) => (a -> b) -> f a -> [NonEmpty a]
+   , groupAllWith  -- :: (Foldable f, Ord b) => (a -> b) -> f a -> [NonEmpty a]
    , group1      -- :: Eq a => NonEmpty a -> NonEmpty (NonEmpty a)
    , groupBy1    -- :: (a -> a -> Bool) -> NonEmpty a -> NonEmpty (NonEmpty a)
+   , groupWith1     -- :: (Foldable f, Eq b) => (a -> b) -> f a -> NonEmpty (NonEmpty a)
+   , groupAllWith1  -- :: (Foldable f, Ord b) => (a -> b) -> f a -> NonEmpty (NonEmpty a)
    -- * Sublist predicates
    , isPrefixOf  -- :: Foldable f => f a -> NonEmpty a -> Bool
    -- * \"Set\" operations
@@ -148,6 +152,7 @@
 import Data.Traversable
 #endif
 import qualified Data.Foldable as Foldable
+import Data.Function (on)
 
 #ifdef MIN_VERSION_hashable
 import Data.Hashable
@@ -510,6 +515,18 @@
     go eq (x : xs) = (x :| ys) : groupBy eq zs
       where (ys, zs) = List.span (eq x) xs
 
+-- | 'groupWith' operates like 'group', but uses the provided projection when
+-- comparing for equality
+groupWith :: (Foldable f, Eq b) => (a -> b) -> f a -> [NonEmpty a]
+groupWith f = groupBy ((==) `on` f)
+{-# INLINE groupWith #-}
+
+-- | 'groupAllWith' operates like 'groupWith', but sorts the list first so that each
+-- equivalence class has, at most, one list in the output
+groupAllWith :: (Ord b) => (a -> b) -> [a] -> [NonEmpty a]
+groupAllWith f = groupWith f . List.sortBy (compare `on` f)
+{-# INLINE groupAllWith #-}
+
 -- | 'group1' operates like 'group', but uses the knowledge that its
 -- input is non-empty to produce guaranteed non-empty output.
 group1 :: Eq a => NonEmpty a -> NonEmpty (NonEmpty a)
@@ -522,6 +539,16 @@
   where (ys, zs) = List.span (eq x) xs
 {-# INLINE groupBy1 #-}
 
+-- | 'groupWith1' is to 'group1' as 'groupWith' is to 'group'
+groupWith1 :: (Eq b) => (a -> b) -> NonEmpty a -> NonEmpty (NonEmpty a)
+groupWith1 f = groupBy1 ((==) `on` f)
+{-# INLINE groupWith1 #-}
+
+-- | 'groupAllWith1' is to 'groupWith1' as 'groupAllWith' is to 'groupWith'
+groupAllWith1 :: (Ord b) => (a -> b) -> NonEmpty a -> NonEmpty (NonEmpty a)
+groupAllWith1 f = groupWith1 f . sortWith f
+{-# INLINE groupAllWith1 #-}
+
 -- | The 'isPrefix' function returns @True@ if the first argument is
 -- a prefix of the second.
 isPrefixOf :: Eq a => [a] -> NonEmpty a -> Bool
@@ -614,8 +641,8 @@
 sortBy :: (a -> a -> Ordering) -> NonEmpty a -> NonEmpty a
 sortBy f = lift (List.sortBy f)
 
--- | 'sortOn' for 'NonEmpty', behaves the same as:
+-- | 'sortWith' for 'NonEmpty', behaves the same as:
 --
 -- > sortBy . comparing
-sortOn :: Ord o => (a -> o) -> NonEmpty a -> NonEmpty a
-sortOn = sortBy . comparing
+sortWith :: Ord o => (a -> o) -> NonEmpty a -> NonEmpty a
+sortWith = sortBy . comparing
diff --git a/src/Data/Semigroup.hs b/src/Data/Semigroup.hs
--- a/src/Data/Semigroup.hs
+++ b/src/Data/Semigroup.hs
@@ -22,6 +22,10 @@
 {-# LANGUAGE FlexibleContexts #-}
 #endif
 
+#if __GLASGOW_HASKELL__ >= 706
+{-# LANGUAGE PolyKinds #-}
+#endif
+
 #if __GLASGOW_HASKELL__ >= 708
 #define USE_COERCE
 {-# LANGUAGE ScopedTypeVariables #-}
@@ -58,13 +62,16 @@
 ----------------------------------------------------------------------------
 module Data.Semigroup (
     Semigroup(..)
+  , stimesMonoid
+  , stimesIdempotent
+  , stimesIdempotentMonoid
+  , mtimesDefault
   -- * Semigroups
   , Min(..)
   , Max(..)
   , First(..)
   , Last(..)
   , WrappedMonoid(..)
-  , timesN
   -- * Re-exported monoids from Data.Monoid
   , Monoid(..)
   , Dual(..)
@@ -89,6 +96,7 @@
 
 #if MIN_VERSION_base(4,8,0)
 import Data.Bifunctor
+import Data.Void
 #else
 import Data.Monoid (Monoid(..))
 import Data.Foldable
@@ -96,13 +104,15 @@
 #endif
 
 import Data.Monoid (Dual(..),Endo(..),All(..),Any(..),Sum(..),Product(..))
+#if MIN_VERSION_base(4,8,0)
+import Data.Monoid (Alt(..))
+#endif
 
 import Control.Applicative
 import Control.Monad
 import Control.Monad.Fix
 import qualified Data.Monoid as Monoid
 import Data.List.NonEmpty
-import Numeric.Natural
 
 #ifdef MIN_VERSION_deepseq
 import Control.DeepSeq (NFData(..))
@@ -131,6 +141,14 @@
 # endif
 #endif
 
+#if MIN_VERSION_base(4,7,0) || defined(MIN_VERSION_tagged)
+import Data.Proxy
+#endif
+
+#ifdef MIN_VERSION_tagged
+import Data.Tagged
+#endif
+
 #ifdef MIN_VERSION_text
 import qualified Data.Text as Strict
 import qualified Data.Text.Lazy as Lazy
@@ -187,19 +205,19 @@
     go b (c:cs) = b <> go c cs
     go b []     = b
 
-  -- | Repeat a value (n + 1) times.
+  -- | Repeat a value @n@ times.
   --
-  -- @
-  -- 'times1p' n a = a '<>' a '<>' ... '<>' a  -- using '<>' n times
-  -- @
+  -- Given that this works on a 'Semigroup' it is allowed to fail if you request 0 or fewer
+  -- repetitions, and the default definition will do so.
   --
-  -- The default definition uses peasant multiplication, exploiting associativity to only
-  -- require /O(log n)/ uses of @\<\>@.
+  -- By making this a member of the class, idempotent semigroups and monoids can upgrade this to execute in
+  -- /O(1)/ by picking @stimes = stimesIdempotent@ or @stimes = stimesIdempotentMonoid@ respectively.
   --
-  -- See also 'timesN'.
-
-  times1p :: Natural -> a -> a
-  times1p y0 x0 = f x0 (1 Prelude.+ y0)
+  -- @since 0.18
+  stimes :: Integral b => b -> a -> a
+  stimes y0 x0
+    | y0 <= 0   = error "stimes: positive multiplier expected"
+    | otherwise = f x0 y0
     where
       f x y
         | even y = f (x <> x) (y `quot` 2)
@@ -209,7 +227,7 @@
         | even y = g (x <> x) (y `quot` 2) z
         | y == 1 = x <> z
         | otherwise = g (x <> x) (pred y `quot` 2) (x <> z)
-  {-# INLINE times1p #-}
+  {-# INLINE stimes #-}
 
 -- | A generalization of 'Data.List.cycle' to an arbitrary 'Semigroup'.
 -- May fail to terminate for some values in some semigroups.
@@ -219,51 +237,61 @@
 instance Semigroup () where
   _ <> _ = ()
   sconcat _ = ()
-  times1p _ _ = ()
+  stimes _ _ = ()
 
 instance Semigroup b => Semigroup (a -> b) where
   f <> g = \a -> f a <> g a
-  times1p n f e = times1p n (f e)
+  stimes n f e = stimes n (f e)
 
 instance Semigroup [a] where
   (<>) = (++)
-  times1p n x = rep n where
-    rep 0 = x
-    rep i = x ++ rep (i - 1)
+  stimes n x
+    | n < 0 = error "stimes: [], negative multiplier"
+    | otherwise = rep n
+    where
+      rep 0 = []
+      rep i = x ++ rep (i - 1)
 
 instance Semigroup a => Semigroup (Maybe a) where
   Nothing <> b       = b
   a       <> Nothing = a
   Just a  <> Just b  = Just (a <> b)
+  stimes _ Nothing  = Nothing
+  stimes n (Just a) = case compare n 0 of
+    LT -> error "stimes: Maybe, negative multiplier"
+    EQ -> Nothing
+    GT -> Just (stimes n a)
 
 instance Semigroup (Either a b) where
   Left _ <> b = b
   a      <> _ = a
+  stimes = stimesIdempotent
 
 instance (Semigroup a, Semigroup b) => Semigroup (a, b) where
   (a,b) <> (a',b') = (a<>a',b<>b')
-  times1p n (a,b) = (times1p n a, times1p n b)
+  stimes n (a,b) = (stimes n a, stimes n b)
 
 instance (Semigroup a, Semigroup b, Semigroup c) => Semigroup (a, b, c) where
   (a,b,c) <> (a',b',c') = (a<>a',b<>b',c<>c')
-  times1p n (a,b,c) = (times1p n a, times1p n b, times1p n c)
+  stimes n (a,b,c) = (stimes n a, stimes n b, stimes n c)
 
 instance (Semigroup a, Semigroup b, Semigroup c, Semigroup d) => Semigroup (a, b, c, d) where
   (a,b,c,d) <> (a',b',c',d') = (a<>a',b<>b',c<>c',d<>d')
-  times1p n (a,b,c,d) = (times1p n a, times1p n b, times1p n c, times1p n d)
+  stimes n (a,b,c,d) = (stimes n a, stimes n b, stimes n c, stimes n d)
 
 instance (Semigroup a, Semigroup b, Semigroup c, Semigroup d, Semigroup e) => Semigroup (a, b, c, d, e) where
   (a,b,c,d,e) <> (a',b',c',d',e') = (a<>a',b<>b',c<>c',d<>d',e<>e')
-  times1p n (a,b,c,d,e) = (times1p n a, times1p n b, times1p n c, times1p n d, times1p n e)
+  stimes n (a,b,c,d,e) = (stimes n a, stimes n b, stimes n c, stimes n d, stimes n e)
 
 instance Semigroup Ordering where
   LT <> _ = LT
   EQ <> y = y
   GT <> _ = GT
+  stimes = stimesIdempotentMonoid
 
 instance Semigroup a => Semigroup (Dual a) where
   Dual a <> Dual b = Dual (b <> a)
-  times1p n (Dual a) = Dual (times1p n a)
+  stimes n (Dual a) = Dual (stimes n a)
 
 instance Semigroup (Endo a) where
 #ifdef USE_COERCE
@@ -271,6 +299,7 @@
 #else
   Endo f <> Endo g = Endo (f . g)
 #endif
+  stimes = stimesMonoid
 
 instance Semigroup All where
 #ifdef USE_COERCE
@@ -278,22 +307,26 @@
 #else
   All a <> All b = All (a && b)
 #endif
-  times1p _ a = a
 
+  stimes = stimesIdempotentMonoid
+
 instance Semigroup Any where
 #ifdef USE_COERCE
   (<>) = coerce (||)
 #else
   Any a <> Any b = Any (a || b)
 #endif
-  times1p _ a = a
 
+  stimes = stimesIdempotentMonoid
+
+
 instance Num a => Semigroup (Sum a) where
 #ifdef USE_COERCE
   (<>) = coerce ((+) :: a -> a -> a)
 #else
   Sum a <> Sum b = Sum (a + b)
 #endif
+  stimes n (Sum a) = Sum (fromIntegral n * a)
 
 instance Num a => Semigroup (Product a) where
 #ifdef USE_COERCE
@@ -301,26 +334,84 @@
 #else
   Product a <> Product b = Product (a * b)
 #endif
+  stimes n (Product a) = Product (a ^ n)
 
+-- | This is a valid definition of 'stimes' for a 'Monoid'.
+-- 
+-- Unlike the default definition of 'stimes', it is defined for 0
+-- and so it should be preferred where possible.
+stimesMonoid :: (Integral b, Monoid a) => b -> a -> a
+stimesMonoid n x0 = case compare n 0 of
+  LT -> error "stimesMonoid: negative multiplier"
+  EQ -> mempty
+  GT -> f x0 n
+    where
+      f x y
+        | even y = f (x `mappend` x) (y `quot` 2)
+        | y == 1 = x
+        | otherwise = g (x `mappend` x) (pred y  `quot` 2) x
+      g x y z
+        | even y = g (x `mappend` x) (y `quot` 2) z
+        | y == 1 = x `mappend` z
+        | otherwise = g (x `mappend` x) (pred y `quot` 2) (x `mappend` z)
+
+-- | This is a valid definition of 'stimes' for an idempotent 'Monoid'.
+--
+-- When @mappend x x = x@, this definition should be preferred, because it
+-- works in /O(1)/ rather than /O(log n)/
+stimesIdempotentMonoid :: (Integral b, Monoid a) => b -> a -> a
+stimesIdempotentMonoid n x = case compare n 0 of
+  LT -> error "stimesIdempotentMonoid: negative multiplier"
+  EQ -> mempty
+  GT -> x
+{-# INLINE stimesIdempotentMonoid #-}
+
+-- | This is a valid definition of 'stimes' for an idempotent 'Semigroup'.
+--
+-- When @x <> x = x@, this definition should be preferred, because it
+-- works in /O(1)/ rather than /O(log n)/.
+stimesIdempotent :: Integral b => b -> a -> a
+stimesIdempotent n x 
+  | n <= 0 = error "stimesIdempotent: positive multiplier expected"
+  | otherwise = x
+{-# INLINE stimesIdempotent #-}
+
 instance Semigroup a => Semigroup (Const a b) where
 #ifdef USE_COERCE
   (<>) = coerce ((<>) :: a -> a -> a)
 #else
   Const a <> Const b = Const (a <> b)
 #endif
+  stimes n (Const a) = Const (stimes n a)
 
 #if MIN_VERSION_base(3,0,0)
 instance Semigroup (Monoid.First a) where
   Monoid.First Nothing <> b = b
   a                    <> _ = a
-  times1p _ a = a
+  stimes = stimesIdempotentMonoid
 
 instance Semigroup (Monoid.Last a) where
   a <> Monoid.Last Nothing = a
   _ <> b                   = b
-  times1p _ a = a
+  stimes = stimesIdempotentMonoid
 #endif
 
+#if MIN_VERSION_base(4,8,0)
+instance Alternative f => Semigroup (Alt f a) where
+# ifdef USE_COERCE
+  (<>) = coerce ((<|>) :: f a -> f a -> f a)
+# else
+  Alt a <> Alt b = Alt (a <|> b)
+# endif
+  stimes = stimesMonoid
+#endif
+
+#if MIN_VERSION_base(4,8,0)
+instance Semigroup Void where
+  a <> _ = a
+  stimes = stimesIdempotent
+#endif
+
 instance Semigroup (NonEmpty a) where
   (a :| as) <> ~(b :| bs) = a :| (as ++ b : bs)
 
@@ -367,7 +458,7 @@
 #else
   Min a <> Min b = Min (a `min` b)
 #endif
-  times1p _ a = a
+  stimes = stimesIdempotent
 
 instance (Ord a, Bounded a) => Monoid (Min a) where
   mempty = maxBound
@@ -401,6 +492,15 @@
   rnf (Min a) = rnf a
 #endif
 
+instance Num a => Num (Min a) where
+  (Min a) + (Min b) = Min (a + b)
+  (Min a) * (Min b) = Min (a * b)
+  (Min a) - (Min b) = Min (a - b)
+  negate (Min a) = Min (negate a)
+  abs    (Min a) = Min (abs a)
+  signum (Min a) = Min (signum a)
+  fromInteger    = Min . fromInteger
+
 newtype Max a = Max { getMax :: a } deriving
   ( Eq, Ord, Show, Read
 #ifdef LANGUAGE_DeriveDataTypeable
@@ -443,7 +543,7 @@
 #else
   Max a <> Max b = Max (a `max` b)
 #endif
-  times1p _ a = a
+  stimes = stimesIdempotent
 
 instance (Ord a, Bounded a) => Monoid (Max a) where
   mempty = minBound
@@ -477,6 +577,16 @@
   rnf (Max a) = rnf a
 #endif
 
+instance Num a => Num (Max a) where
+  (Max a) + (Max b) = Max (a + b)
+  (Max a) * (Max b) = Max (a * b)
+  (Max a) - (Max b) = Max (a - b)
+  negate (Max a) = Max (negate a)
+  abs    (Max a) = Max (abs a)
+  signum (Max a) = Max (signum a)
+  fromInteger    = Max . fromInteger
+
+
 -- | 'Arg' isn't itself a 'Semigroup' in its own right, but it can be placed inside 'Min' and 'Max'
 -- to compute an arg min or arg max.
 data Arg a b = Arg a b deriving
@@ -575,7 +685,7 @@
 
 instance Semigroup (First a) where
   a <> _ = a
-  times1p _ a = a
+  stimes = stimesIdempotent
 
 instance Functor First where
   fmap f (First x) = First (f x)
@@ -644,7 +754,7 @@
 
 instance Semigroup (Last a) where
   _ <> b = b
-  times1p _ a = a
+  stimes = stimesIdempotent
 
 instance Functor Last where
   fmap f (Last x) = Last (f x)
@@ -709,10 +819,11 @@
 #ifdef MIN_VERSION_unordered_containers
 instance (Hashable k, Eq k) => Semigroup (Lazy.HashMap k a) where
   (<>) = mappend
+  stimes = stimesIdempotentMonoid
 
 instance (Hashable a, Eq a) => Semigroup (HashSet a) where
   (<>) = mappend
-  times1p _ a = a
+  stimes = stimesIdempotentMonoid
 #endif
 
 -- | Provide a Semigroup for an arbitrary Monoid.
@@ -771,14 +882,17 @@
 
 -- | Repeat a value @n@ times.
 --
--- > timesN n a = a <> a <> ... <> a  -- using <> (n-1) times
+-- > mtimesDefault n a = a <> a <> ... <> a  -- using <> (n-1) times
 --
--- Implemented using 'times1p'.
-timesN :: Monoid a => Natural -> a -> a
-timesN n x | n == 0    = mempty
-           | otherwise = unwrapMonoid . times1p (pred n) . WrapMonoid $ x
-{-# INLINE timesN #-}
-
+-- Implemented using 'stimes' and 'mempty'.
+--
+-- This is a suitable definition for an 'mtimes' member of 'Monoid'.
+--
+-- @since 0.18
+mtimesDefault :: (Integral b, Monoid a) => b -> a -> a
+mtimesDefault n x
+  | n == 0    = mempty
+  | otherwise = unwrapMonoid (stimes n (WrapMonoid x))
 
 -- | 'Option' is effectively 'Maybe' with a better instance of 'Monoid', built off of an underlying 'Semigroup'
 -- instead of an underlying 'Monoid'.
@@ -858,6 +972,11 @@
 #else
   Option a <> Option b = Option (a <> b)
 #endif
+  stimes _ (Option Nothing) = Option Nothing
+  stimes n (Option (Just a)) = case compare n 0 of
+    LT -> error "stimes: Option, negative multiplier"
+    EQ -> Option Nothing
+    GT -> Option (Just (stimes n a))
 
 instance Semigroup a => Monoid (Option a) where
   mempty = Option Nothing
@@ -873,17 +992,34 @@
 
 instance Semigroup IntSet where
   (<>) = mappend
-  times1p _ a = a
+  stimes = stimesIdempotentMonoid
 
 instance Ord a => Semigroup (Set a) where
   (<>) = mappend
-  times1p _ a = a
+  stimes = stimesIdempotentMonoid
 
 instance Semigroup (IntMap v) where
   (<>) = mappend
-  times1p _ a = a
+  stimes = stimesIdempotentMonoid
 
 instance Ord k => Semigroup (Map k v) where
   (<>) = mappend
-  times1p _ a = a
+  stimes = stimesIdempotentMonoid
 #endif
+
+#if MIN_VERSION_base(4,7,0) || defined(MIN_VERSION_tagged)
+instance Semigroup (Proxy s) where
+  _ <> _ = Proxy
+  sconcat _ = Proxy
+  stimes _ _ = Proxy
+#endif
+
+#ifdef MIN_VERSION_tagged
+instance Semigroup a => Semigroup (Tagged s a) where
+# ifdef USE_COERCE
+  (<>) = coerce ((<>) :: a -> a -> a)
+# else
+  Tagged a <> Tagged b = Tagged (a <> b)
+# endif
+#endif
+  stimes n (Tagged a) = Tagged (stimes n a)
