diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,9 @@
 Changelog for Extra
 
+1.7.11, released 2022-08-21
+    #95, add List.repeatedlyNE and NonEmpty.repeatedly
+    #94, add groupOnKey
+    #91, add foldl1' to NonEmpty
 1.7.10, released 2021-08-29
     #81, add assertIO function
     #85, add !? to do !! list indexing returning a Maybe
diff --git a/Generate.hs b/Generate.hs
--- a/Generate.hs
+++ b/Generate.hs
@@ -81,7 +81,7 @@
 
 hidden :: String -> [String]
 hidden "Data.List.NonEmpty.Extra" = words
-    "cons snoc sortOn union unionBy nubOrd nubOrdBy nubOrdOn (!?)"
+    "cons snoc sortOn union unionBy nubOrd nubOrdBy nubOrdOn (!?) foldl1' repeatedly"
 hidden _ = []
 
 notHidden :: String -> String -> Bool
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright Neil Mitchell 2014-2021.
+Copyright Neil Mitchell 2014-2022.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
 
 * I have been using most of these functions in my packages - they have proved useful enough to be worth copying/pasting into each project.
 * The functions follow the spirit of the original Prelude/base libraries. I am happy to provide partial functions (e.g. `fromRight`), and functions which are specialisations of more generic functions (`whenJust`).
-* Most of the functions have trivial implementations. If a beginner couldn't write the function, it probably doesn't belong here.
+* Most of the functions have trivial implementations that are obvious from their name/signature. If a beginner couldn't write the function, it probably doesn't belong here.
 * I have defined only a few new data types or type aliases. It's a package for defining new utilities on existing types, not new types or concepts.
 
 ## Base versions
diff --git a/extra.cabal b/extra.cabal
--- a/extra.cabal
+++ b/extra.cabal
@@ -1,13 +1,13 @@
 cabal-version:      1.18
 build-type:         Simple
 name:               extra
-version:            1.7.10
+version:            1.7.11
 license:            BSD3
 license-file:       LICENSE
 category:           Development
 author:             Neil Mitchell <ndmitchell@gmail.com>
 maintainer:         Neil Mitchell <ndmitchell@gmail.com>
-copyright:          Neil Mitchell 2014-2021
+copyright:          Neil Mitchell 2014-2022
 synopsis:           Extra functions I use.
 description:
     A library of extra functions for the standard Haskell libraries. Most functions are simple additions, filling out missing functionality. A few functions are available in later versions of GHC, but this package makes them available back to GHC 7.2.
@@ -15,7 +15,7 @@
     The module "Extra" documents all functions provided by this library. Modules such as "Data.List.Extra" provide extra functions over "Data.List" and also reexport "Data.List". Users are recommended to replace "Data.List" imports with "Data.List.Extra" if they need the extra functionality.
 homepage:           https://github.com/ndmitchell/extra#readme
 bug-reports:        https://github.com/ndmitchell/extra/issues
-tested-with:        GHC==9.0, GHC==8.10, GHC==8.8, GHC==8.6, GHC==8.4, GHC==8.2, GHC==8.0
+tested-with:        GHC==9.0, GHC==8.10, GHC==8.8, GHC==8.6
 
 extra-doc-files:
     CHANGES.txt
diff --git a/src/Control/Monad/Extra.hs b/src/Control/Monad/Extra.hs
--- a/src/Control/Monad/Extra.hs
+++ b/src/Control/Monad/Extra.hs
@@ -30,6 +30,7 @@
 -- General utilities
 
 -- | Perform some operation on 'Just', given the field inside the 'Just'.
+--   This is a specialized 'Data.Foldable.for_'.
 --
 -- > whenJust Nothing  print == pure ()
 -- > whenJust (Just 1) print == print 1
diff --git a/src/Data/List/Extra.hs b/src/Data/List/Extra.hs
--- a/src/Data/List/Extra.hs
+++ b/src/Data/List/Extra.hs
@@ -25,13 +25,13 @@
     -- * List operations
     groupSort, groupSortOn, groupSortBy,
     nubOrd, nubOrdBy, nubOrdOn,
-    nubOn, groupOn,
+    nubOn, groupOn, groupOnKey,
     nubSort, nubSortBy, nubSortOn,
     maximumOn, minimumOn,
     sum', product',
     sumOn', productOn',
     disjoint, disjointOrd, disjointOrdBy, allSame, anySame,
-    repeatedly, firstJust,
+    repeatedly, repeatedlyNE, firstJust,
     concatUnzip, concatUnzip3,
     zipFrom, zipWithFrom, zipWithLongest,
     replace, merge, mergeBy,
@@ -48,11 +48,16 @@
 import Data.Functor
 import Data.Foldable
 import Prelude
+import Data.List.NonEmpty (NonEmpty ((:|)))
 
 
 -- | Apply some operation repeatedly, producing an element of output
 --   and the remainder of the list.
 --
+-- When the empty list is reached it is returned, so the operation
+-- is /never/ applied to the empty input.
+-- That fact is encoded in the type system with 'repeatedlyNE'
+--
 -- > \xs -> repeatedly (splitAt 3) xs  == chunksOf 3 xs
 -- > \xs -> repeatedly word1 (trim xs) == words xs
 -- > \xs -> repeatedly line1 xs == lines xs
@@ -61,7 +66,17 @@
 repeatedly f as = b : repeatedly f as'
     where (b, as') = f as
 
+-- | Apply some operation repeatedly, producing an element of output
+--   and the remainder of the list.
+--
+-- Identical to 'repeatedly', but has a more precise type signature.
+repeatedlyNE :: (NonEmpty a -> (b, [a])) -> [a] -> [b]
+repeatedlyNE f [] = []
+repeatedlyNE f (a : as) = b : repeatedlyNE f as'
+    where (b, as') = f (a :| as)
 
+
+
 -- | Are two lists disjoint, with no elements in common.
 --
 -- > disjoint [1,2,3] [4,5] == True
@@ -72,7 +87,7 @@
 -- | /O((m+n) log m), m <= n/. Are two lists disjoint, with no elements in common.
 --
 -- @disjointOrd@ is more strict than `disjoint`. For example, @disjointOrd@ cannot
--- terminate if both lists are inifite, while `disjoint` can.
+-- terminate if both lists are infinite, while `disjoint` can.
 --
 -- > disjointOrd [1,2,3] [4,5] == True
 -- > disjointOrd [1,2,3] [4,1] == False
@@ -411,10 +426,24 @@
 
 
 -- | A version of 'group' where the equality is done on some extracted value.
-groupOn :: Eq b => (a -> b) -> [a] -> [[a]]
+--
+-- > groupOn abs [1,-1,2] == [[1,-1], [2]]
+groupOn :: Eq k => (a -> k) -> [a] -> [[a]]
 groupOn f = groupBy ((==) `on2` f)
     -- redefine on so we avoid duplicate computation for most values.
     where (.*.) `on2` f = \x -> let fx = f x in \y -> fx .*. f y
+
+
+-- | A version of 'groupOn' which pairs each group with its "key" - the
+--   extracted value used for equality testing.
+--
+-- > groupOnKey abs [1,-1,2] == [(1, [1,-1]), (2, [2])]
+groupOnKey :: Eq k => (a -> k) -> [a] -> [(k, [a])]
+groupOnKey _ []     = []
+groupOnKey f (x:xs) = (fx, x:yes) : groupOnKey f no
+    where
+        fx = f x
+        (yes, no) = span (\y -> fx == f y) xs
 
 
 -- | /DEPRECATED/ Use 'nubOrdOn', since this function is _O(n^2)_.
diff --git a/src/Data/List/NonEmpty/Extra.hs b/src/Data/List/NonEmpty/Extra.hs
--- a/src/Data/List/NonEmpty/Extra.hs
+++ b/src/Data/List/NonEmpty/Extra.hs
@@ -8,7 +8,8 @@
     appendl, appendr,
     sortOn, union, unionBy,
     nubOrd, nubOrdBy, nubOrdOn,
-    maximum1, minimum1, maximumBy1, minimumBy1, maximumOn1, minimumOn1
+    maximum1, minimum1, maximumBy1, minimumBy1, maximumOn1, minimumOn1,
+    foldl1', repeatedly
     ) where
 
 import           Data.Function
@@ -123,3 +124,13 @@
 -- | A version of 'minimum1' where the comparison is done on some extracted value.
 minimumOn1 :: Ord b => (a -> b) -> NonEmpty a -> a
 minimumOn1 f = minimumBy1 (compare `on` f)
+
+-- | A strict variant of variant `foldl1`
+foldl1' :: (a -> a -> a) -> NonEmpty a -> a
+foldl1' f (x:|xs) =  List.foldl' f x xs
+
+-- | Apply some operation repeatedly, producing an element of output
+--   and the remainder of the list.
+repeatedly :: (NonEmpty a -> (b, [a])) -> NonEmpty a -> NonEmpty b
+repeatedly f (a :| as) = b :| List.repeatedlyNE f as'
+    where (b, as') = f (a :| as)
diff --git a/src/Extra.hs b/src/Extra.hs
--- a/src/Extra.hs
+++ b/src/Extra.hs
@@ -23,7 +23,7 @@
     writeIORef', atomicWriteIORef', atomicModifyIORef_, atomicModifyIORef'_,
     -- * Data.List.Extra
     -- | Extra functions available in @"Data.List.Extra"@.
-    lower, upper, trim, trimStart, trimEnd, word1, line1, escapeHTML, escapeJSON, unescapeHTML, unescapeJSON, dropEnd, takeEnd, splitAtEnd, breakEnd, spanEnd, dropWhileEnd', takeWhileEnd, stripSuffix, stripInfix, stripInfixEnd, dropPrefix, dropSuffix, wordsBy, linesBy, breakOn, breakOnEnd, splitOn, split, chunksOf, headDef, lastDef, (!?), notNull, list, unsnoc, cons, snoc, drop1, dropEnd1, mconcatMap, compareLength, comparingLength, enumerate, groupSort, groupSortOn, groupSortBy, nubOrd, nubOrdBy, nubOrdOn, nubOn, groupOn, nubSort, nubSortBy, nubSortOn, maximumOn, minimumOn, sum', product', sumOn', productOn', disjoint, disjointOrd, disjointOrdBy, allSame, anySame, repeatedly, firstJust, concatUnzip, concatUnzip3, zipFrom, zipWithFrom, zipWithLongest, replace, merge, mergeBy,
+    lower, upper, trim, trimStart, trimEnd, word1, line1, escapeHTML, escapeJSON, unescapeHTML, unescapeJSON, dropEnd, takeEnd, splitAtEnd, breakEnd, spanEnd, dropWhileEnd', takeWhileEnd, stripSuffix, stripInfix, stripInfixEnd, dropPrefix, dropSuffix, wordsBy, linesBy, breakOn, breakOnEnd, splitOn, split, chunksOf, headDef, lastDef, (!?), notNull, list, unsnoc, cons, snoc, drop1, dropEnd1, mconcatMap, compareLength, comparingLength, enumerate, groupSort, groupSortOn, groupSortBy, nubOrd, nubOrdBy, nubOrdOn, nubOn, groupOn, groupOnKey, nubSort, nubSortBy, nubSortOn, maximumOn, minimumOn, sum', product', sumOn', productOn', disjoint, disjointOrd, disjointOrdBy, allSame, anySame, repeatedly, repeatedlyNE, firstJust, concatUnzip, concatUnzip3, zipFrom, zipWithFrom, zipWithLongest, replace, merge, mergeBy,
     -- * Data.List.NonEmpty.Extra
     -- | Extra functions available in @"Data.List.NonEmpty.Extra"@.
     (|:), (|>), appendl, appendr, maximum1, minimum1, maximumBy1, minimumBy1, maximumOn1, minimumOn1,
@@ -59,7 +59,7 @@
 import Data.Either.Extra
 import Data.IORef.Extra
 import Data.List.Extra
-import Data.List.NonEmpty.Extra hiding (cons, snoc, sortOn, union, unionBy, nubOrd, nubOrdBy, nubOrdOn, (!?))
+import Data.List.NonEmpty.Extra hiding (cons, snoc, sortOn, union, unionBy, nubOrd, nubOrdBy, nubOrdOn, (!?), foldl1', repeatedly)
 import Data.Tuple.Extra
 import Data.Version.Extra
 import Numeric.Extra
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -16,5 +16,6 @@
 
 main :: IO ()
 main = runTests $ do
+    testSetup
     tests
     testCustom
diff --git a/test/TestCustom.hs b/test/TestCustom.hs
--- a/test/TestCustom.hs
+++ b/test/TestCustom.hs
@@ -1,5 +1,5 @@
 
-module TestCustom(testCustom) where
+module TestCustom(testSetup, testCustom) where
 
 import Control.Concurrent.Extra
 import Control.Monad
@@ -9,6 +9,14 @@
 import Data.List.Extra as X
 
 
+-- | Test that the basic test mechanisms work
+testSetup :: IO ()
+testSetup = do
+    testGen "withTempFile" $ withTempFile (`writeFile` "") == pure ()
+    testGen "captureOutput" $ captureOutput (print 1) == pure ("1\n", ())
+
+
+-- | Custom written tests
 testCustom :: IO ()
 testCustom = do
     -- check that Extra really does export these things
diff --git a/test/TestGen.hs b/test/TestGen.hs
--- a/test/TestGen.hs
+++ b/test/TestGen.hs
@@ -171,6 +171,8 @@
     testGen "escapeJSON \"\\ttab\\nnewline\\\\\" == \"\\\\ttab\\\\nnewline\\\\\\\\\"" $ escapeJSON "\ttab\nnewline\\" == "\\ttab\\nnewline\\\\"
     testGen "escapeJSON \"\\ESC[0mHello\" == \"\\\\u001b[0mHello\"" $ escapeJSON "\ESC[0mHello" == "\\u001b[0mHello"
     testGen "\\xs -> unescapeJSON (escapeJSON xs) == xs" $ \xs -> unescapeJSON (escapeJSON xs) == xs
+    testGen "groupOn abs [1,-1,2] == [[1,-1], [2]]" $ groupOn abs [1,-1,2] == [[1,-1], [2]]
+    testGen "groupOnKey abs [1,-1,2] == [(1, [1,-1]), (2, [2])]" $ groupOnKey abs [1,-1,2] == [(1, [1,-1]), (2, [2])]
     testGen "maximumOn id [] == undefined" $ erroneous $ maximumOn id []
     testGen "maximumOn length [\"test\",\"extra\",\"a\"] == \"extra\"" $ maximumOn length ["test","extra","a"] == "extra"
     testGen "minimumOn id [] == undefined" $ erroneous $ minimumOn id []
diff --git a/test/TestUtil.hs b/test/TestUtil.hs
--- a/test/TestUtil.hs
+++ b/test/TestUtil.hs
@@ -83,8 +83,8 @@
 -- So we print out full information on failure
 instance (Show a, Eq a) => Eq (IO a) where
     a == b = unsafePerformIO $ do
-        a <- try_ $ captureOutput a
-        b <- try_ $ captureOutput b
+        a <- captureOutput $ try_ a
+        b <- captureOutput $ try_ b
         if a == b then pure True else
             error $ show ("IO values not equal", a, b)
 
