diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,8 @@
 Changelog for Extra
 
+1.6.21, released 2020-03-02
+    #54, deprecate nubOn since its O(n^2). Use nubOrdOn
+    #53, add some nub functions to NonEmpty
 1.6.20, released 2020-02-16
     Add firstM, secondM
 1.6.19, released 2020-02-11
diff --git a/Generate.hs b/Generate.hs
--- a/Generate.hs
+++ b/Generate.hs
@@ -25,7 +25,7 @@
                     words $ replace "," " " $ drop1 $ dropWhile (/= '(') $
                     unlines $ filter (\x -> not $ any (`isPrefixOf` trim x) ["--","#"]) $ lines src
         let tests = mapMaybe (stripPrefix "-- > ") $ lines src
-        return (mod, funcs, tests)
+        pure (mod, funcs, tests)
     writeFileBinaryChanged "src/Extra.hs" $ unlines $
         ["-- GENERATED CODE - DO NOT MODIFY"
         ,"-- See Generate.hs for details of how to generate"
@@ -49,6 +49,9 @@
         ,"{-# LANGUAGE ExtendedDefaultRules, ScopedTypeVariables, ViewPatterns #-}"
         ,"module TestGen(tests) where"
         ,"import TestUtil"
+        ,"import qualified Data.List"
+        ,"import qualified Data.List.NonEmpty.Extra"
+        ,"import Test.QuickCheck.Instances.Semigroup ()"
         ,"default(Maybe Bool,Int,Double,Maybe (Maybe Bool),Maybe (Maybe Char))"
         ,"tests :: IO ()"
         ,"tests = do"] ++
@@ -62,12 +65,14 @@
 writeFileBinaryChanged :: FilePath -> String -> IO ()
 writeFileBinaryChanged file x = do
     evaluate $ length x -- ensure we don't write out files with _|_ in them
-    old <- ifM (doesFileExist file) (Just <$> readFileBinary' file) (return Nothing)
+    old <- ifM (doesFileExist file) (Just <$> readFileBinary' file) (pure Nothing)
     when (Just x /= old) $
         writeFileBinary file x
 
 hidden :: String -> [String]
-hidden "Data.List.NonEmpty.Extra" = ["cons", "snoc", "sortOn", "union", "unionBy"]
+hidden "Data.List.NonEmpty.Extra" = [ "cons", "snoc", "sortOn", "union", "unionBy"
+                                    , "nubOrd", "nubOrdBy", "nubOrdOn"
+                                    ]
 hidden _ = []
 
 notHidden :: String -> String -> Bool
diff --git a/extra.cabal b/extra.cabal
--- a/extra.cabal
+++ b/extra.cabal
@@ -1,7 +1,7 @@
 cabal-version:      >= 1.18
 build-type:         Simple
 name:               extra
-version:            1.6.20
+version:            1.6.21
 license:            BSD3
 license-file:       LICENSE
 category:           Development
@@ -73,7 +73,8 @@
         directory,
         filepath,
         extra,
-        QuickCheck >= 2.10
+        QuickCheck >= 2.10,
+        quickcheck-instances >= 0.3.17
     if !os(windows)
         build-depends: unix
     hs-source-dirs: test
diff --git a/src/Control/Concurrent/Extra.hs b/src/Control/Concurrent/Extra.hs
--- a/src/Control/Concurrent/Extra.hs
+++ b/src/Control/Concurrent/Extra.hs
@@ -46,23 +46,23 @@
 --   If the function raises an exception, the same exception will be reraised each time.
 --
 -- > let x ||| y = do t1 <- onceFork x; t2 <- onceFork y; t1; t2
--- > \(x :: IO Int) -> void (once x) == return ()
+-- > \(x :: IO Int) -> void (once x) == pure ()
 -- > \(x :: IO Int) -> join (once x) == x
 -- > \(x :: IO Int) -> (do y <- once x; y; y) == x
 -- > \(x :: IO Int) -> (do y <- once x; y ||| y) == x
 once :: IO a -> IO (IO a)
 once act = do
     var <- newVar OncePending
-    let run = either throwIO return
-    return $ mask $ \unmask -> join $ modifyVar var $ \v -> case v of
-        OnceDone x -> return (v, unmask $ run x)
-        OnceRunning x -> return (v, unmask $ run =<< waitBarrier x)
+    let run = either throwIO pure
+    pure $ mask $ \unmask -> join $ modifyVar var $ \v -> case v of
+        OnceDone x -> pure (v, unmask $ run x)
+        OnceRunning x -> pure (v, unmask $ run =<< waitBarrier x)
         OncePending -> do
             b <- newBarrier
-            return $ (OnceRunning b,) $ do
+            pure $ (OnceRunning b,) $ do
                 res <- try_ $ unmask act
                 signalBarrier b res
-                modifyVar_ var $ \_ -> return $ OnceDone res
+                modifyVar_ var $ \_ -> pure $ OnceDone res
                 run res
 
 data Once a = OncePending | OnceRunning (Barrier a) | OnceDone a
@@ -76,7 +76,7 @@
 onceFork act = do
     bar <- newBarrier
     forkFinally act $ signalBarrier bar
-    return $ eitherM throwIO return $ waitBarrier bar
+    pure $ eitherM throwIO pure $ waitBarrier bar
 
 
 ---------------------------------------------------------------------
@@ -115,7 +115,7 @@
 withLockTry (Lock m) act = bracket
     (tryTakeMVar m)
     (\v -> when (isJust v) $ putMVar m ())
-    (\v -> if isJust v then fmap Just act else return Nothing)
+    (\v -> if isJust v then fmap Just act else pure Nothing)
 
 
 ---------------------------------------------------------------------
@@ -150,7 +150,7 @@
 
 -- | Write a value to become the new value of 'Var'.
 writeVar :: Var a -> a -> IO ()
-writeVar v x = modifyVar_ v $ const $ return x
+writeVar v x = modifyVar_ v $ const $ pure x
 
 -- | Modify a 'Var' producing a new value and a return result.
 modifyVar :: Var a -> (a -> IO (a, b)) -> IO b
@@ -195,7 +195,7 @@
 signalBarrier :: Partial => Barrier a -> a -> IO ()
 signalBarrier (Barrier var) v = mask_ $ -- use mask so never in an inconsistent state
     join $ modifyVar var $ \x -> case x of
-        Left bar -> return (Right v, putMVar bar ())
+        Left bar -> pure (Right v, putMVar bar ())
         Right res -> error "Control.Concurrent.Extra.signalBarrier, attempt to signal a barrier that has already been signaled"
 
 
@@ -204,12 +204,12 @@
 waitBarrier (Barrier var) = do
     x <- readVar var
     case x of
-        Right res -> return res
+        Right res -> pure res
         Left bar -> do
             readMVar bar
             x <- readVar var
             case x of
-                Right res -> return res
+                Right res -> pure res
                 Left bar -> error "Control.Concurrent.Extra, internal invariant violated in Barrier"
 
 
diff --git a/src/Control/Exception/Extra.hs b/src/Control/Exception/Extra.hs
--- a/src/Control/Exception/Extra.hs
+++ b/src/Control/Exception/Extra.hs
@@ -35,16 +35,16 @@
 
 -- | Fully evaluate an input String. If the String contains embedded exceptions it will produce @\<Exception\>@.
 --
--- > stringException "test"                           == return "test"
--- > stringException ("test" ++ undefined)            == return "test<Exception>"
--- > stringException ("test" ++ undefined ++ "hello") == return "test<Exception>"
--- > stringException ['t','e','s','t',undefined]      == return "test<Exception>"
+-- > stringException "test"                           == pure "test"
+-- > stringException ("test" ++ undefined)            == pure "test<Exception>"
+-- > stringException ("test" ++ undefined ++ "hello") == pure "test<Exception>"
+-- > stringException ['t','e','s','t',undefined]      == pure "test<Exception>"
 stringException :: String -> IO String
 stringException x = do
     r <- try_ $ evaluate $ list [] (\x xs -> x `seq` x:xs) x
     case r of
-        Left e -> return "<Exception>"
-        Right [] -> return []
+        Left e -> pure "<Exception>"
+        Right [] -> pure []
         Right (x:xs) -> (x:) <$> stringException xs
 
 
@@ -66,7 +66,7 @@
 -- | Ignore any exceptions thrown by the action.
 --
 -- > ignore (print 1)    == print 1
--- > ignore (fail "die") == return ()
+-- > ignore (fail "die") == pure ()
 ignore :: IO () -> IO ()
 ignore = void . try_
 
@@ -74,7 +74,7 @@
 -- | An 'IO' action that when evaluated calls 'error', in the 'IO' monad.
 --   Note that while 'fail' in 'IO' raises an 'IOException', this function raises an 'ErrorCall' exception with a call stack.
 --
--- > catch (errorIO "Hello") (\(ErrorCall x) -> return x) == return "Hello"
+-- > catch (errorIO "Hello") (\(ErrorCall x) -> pure x) == pure "Hello"
 -- > seq (errorIO "foo") (print 1) == print 1
 errorIO :: Partial => String -> IO a
 errorIO x = withFrozenCallStack $ evaluate $ error x
@@ -103,7 +103,7 @@
     res <- tryBool p x
     case res of
         Left _ -> retryBool p (i-1) x
-        Right v -> return v
+        Right v -> pure v
 
 
 -- | A version of 'catch' without the 'Exception' context, restricted to 'SomeException',
@@ -135,7 +135,7 @@
 --   As an example:
 --
 -- @
--- readFileExists x == catchBool isDoesNotExistError (readFile \"myfile\") (const $ return \"\")
+-- readFileExists x == catchBool isDoesNotExistError (readFile \"myfile\") (const $ pure \"\")
 -- @
 catchBool :: Exception e => (e -> Bool) -> IO a -> (e -> IO a) -> IO a
 catchBool f a b = catchJust (bool f) a b
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,7 +30,7 @@
 
 -- | Perform some operation on 'Just', given the field inside the 'Just'.
 --
--- > whenJust Nothing  print == return ()
+-- > whenJust Nothing  print == pure ()
 -- > whenJust (Just 1) print == print 1
 whenJust :: Applicative m => Maybe a -> (a -> m ()) -> m ()
 whenJust mg f = maybe (pure ()) f mg
@@ -38,14 +38,14 @@
 -- | Like 'whenJust', but where the test can be monadic.
 whenJustM :: Monad m => m (Maybe a) -> (a -> m ()) -> m ()
 -- Can't reuse whenMaybe on GHC 7.8 or lower because Monad does not imply Applicative
-whenJustM mg f = maybeM (return ()) f mg
+whenJustM mg f = maybeM (pure ()) f mg
 
 
 -- | Like 'when', but return either 'Nothing' if the predicate was 'False',
 --   of 'Just' with the result of the computation.
 --
 -- > whenMaybe True  (print 1) == fmap Just (print 1)
--- > whenMaybe False (print 1) == return Nothing
+-- > whenMaybe False (print 1) == pure Nothing
 whenMaybe :: Applicative m => Bool -> m a -> m (Maybe a)
 whenMaybe b x = if b then Just <$> x else pure Nothing
 
@@ -54,7 +54,7 @@
 -- Can't reuse whenMaybe on GHC 7.8 or lower because Monad does not imply Applicative
 whenMaybeM mb x = do
     b <- mb
-    if b then liftM Just x else return Nothing
+    if b then liftM Just x else pure Nothing
 
 
 -- | The identity function which requires the inner argument to be @()@. Useful for functions
@@ -89,7 +89,7 @@
 
 -- | Like 'fold1M' but discards the result.
 fold1M_ :: (Partial, Monad m) => (a -> a -> m a) -> [a] -> m ()
-fold1M_ f xs = fold1M f xs >> return ()
+fold1M_ f xs = fold1M f xs >> pure ()
 
 
 -- Data.List for Monad
@@ -99,18 +99,18 @@
 -- > partitionM (Just . even) [1,2,3] == Just ([2], [1,3])
 -- > partitionM (const Nothing) [1,2,3] == Nothing
 partitionM :: Monad m => (a -> m Bool) -> [a] -> m ([a], [a])
-partitionM f [] = return ([], [])
+partitionM f [] = pure ([], [])
 partitionM f (x:xs) = do
     res <- f x
     (as,bs) <- partitionM f xs
-    return ([x | res]++as, [x | not res]++bs)
+    pure ([x | res]++as, [x | not res]++bs)
 
 
 -- | A version of 'concatMap' that works with a monadic predicate.
 concatMapM :: Monad m => (a -> m [b]) -> [a] -> m [b]
 {-# INLINE concatMapM #-}
-concatMapM op = foldr f (return [])
-    where f x xs = do x <- op x; if null x then xs else do xs <- xs; return $ x++xs
+concatMapM op = foldr f (pure [])
+    where f x xs = do x <- op x; if null x then xs else do xs <- xs; pure $ x++xs
 
 -- | Like 'concatMapM', but has its arguments flipped, so can be used
 --   instead of the common @fmap concat $ forM@ pattern.
@@ -124,8 +124,8 @@
 -- | A version of 'mapMaybe' that works with a monadic predicate.
 mapMaybeM :: Monad m => (a -> m (Maybe b)) -> [a] -> m [b]
 {-# INLINE mapMaybeM #-}
-mapMaybeM op = foldr f (return [])
-    where f x xs = do x <- op x; case x of Nothing -> xs; Just x -> do xs <- xs; return $ x:xs
+mapMaybeM op = foldr f (pure [])
+    where f x xs = do x <- op x; case x of Nothing -> xs; Just x -> do xs <- xs; pure $ x:xs
 
 -- Looping
 
@@ -145,7 +145,7 @@
     res <- act x
     case res of
         Left x -> loopM act x
-        Right v -> return v
+        Right v -> pure v
 
 -- | Keep running an operation until it becomes 'False'. As an example:
 --
@@ -164,11 +164,11 @@
 
 -- | Like 'when', but where the test can be monadic.
 whenM :: Monad m => m Bool -> m () -> m ()
-whenM b t = ifM b t (return ())
+whenM b t = ifM b t (pure ())
 
 -- | Like 'unless', but where the test can be monadic.
 unlessM :: Monad m => m Bool -> m () -> m ()
-unlessM b f = ifM b (return ()) f
+unlessM b f = ifM b (pure ()) f
 
 -- | Like @if@, but where the test can be monadic.
 ifM :: Monad m => m Bool -> m a -> m a -> m a
@@ -186,7 +186,7 @@
 -- > Just False ||^ Just True  == Just True
 -- > Just False ||^ Just False == Just False
 (||^) :: Monad m => m Bool -> m Bool -> m Bool
-(||^) a b = ifM a (return True) b
+(||^) a b = ifM a (pure True) b
 
 -- | The lazy '&&' operator lifted to a monad. If the first
 --   argument evaluates to 'False' the second argument will not
@@ -196,7 +196,7 @@
 -- > Just True  &&^ Just True  == Just True
 -- > Just True  &&^ Just False == Just False
 (&&^) :: Monad m => m Bool -> m Bool -> m Bool
-(&&^) a b = ifM a b (return False)
+(&&^) a b = ifM a b (pure False)
 
 -- | A version of 'any' lifted to a monad. Retains the short-circuiting behaviour.
 --
@@ -204,7 +204,7 @@
 -- > anyM Just [False,False,undefined] == undefined
 -- > \(f :: Int -> Maybe Bool) xs -> anyM f xs == orM (map f xs)
 anyM :: Monad m => (a -> m Bool) -> [a] -> m Bool
-anyM p = foldr ((||^) . p) (return False)
+anyM p = foldr ((||^) . p) (pure False)
 
 -- | A version of 'all' lifted to a monad. Retains the short-circuiting behaviour.
 --
@@ -212,7 +212,7 @@
 -- > allM Just [True,True ,undefined] == undefined
 -- > \(f :: Int -> Maybe Bool) xs -> anyM f xs == orM (map f xs)
 allM :: Monad m => (a -> m Bool) -> [a] -> m Bool
-allM p = foldr ((&&^) . p) (return True)
+allM p = foldr ((&&^) . p) (pure True)
 
 -- | A version of 'or' lifted to a monad. Retains the short-circuiting behaviour.
 --
@@ -238,9 +238,9 @@
 -- > findM (Just . isUpper) "test"             == Just Nothing
 -- > findM (Just . const True) ["x",undefined] == Just (Just "x")
 findM :: Monad m => (a -> m Bool) -> [a] -> m (Maybe a)
-findM p = foldr (\x -> ifM (p x) (return $ Just x)) (return Nothing)
+findM p = foldr (\x -> ifM (p x) (pure $ Just x)) (pure Nothing)
 
 -- | Like 'findM', but also allows you to compute some additional information in the predicate.
 firstJustM :: Monad m => (a -> m (Maybe b)) -> [a] -> m (Maybe b)
-firstJustM p [] = return Nothing
-firstJustM p (x:xs) = maybeM (firstJustM p xs) (return . Just) (p x)
+firstJustM p [] = pure Nothing
+firstJustM p (x:xs) = maybeM (firstJustM p xs) (pure . Just) (p x)
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
@@ -377,10 +377,13 @@
     where (.*.) `on2` f = \x -> let fx = f x in \y -> fx .*. f y
 
 
--- | A version of 'nub' where the equality is done on some extracted value.
+-- | /DEPRECATED/ Use 'nubOrdOn', since this function is _O(n^2)_.
+--
+--   A version of 'nub' where the equality is done on some extracted value.
 --   @nubOn f@ is equivalent to @nubBy ((==) `on` f)@, but has the
 --   performance advantage of only evaluating @f@ once for each element in the
 --   input list.
+{-# DEPRECATED nubOn "Use nubOrdOn, since this function is O(n^2)" #-}
 nubOn :: Eq b => (a -> b) -> [a] -> [a]
 nubOn f = map snd . nubBy ((==) `on` fst) . map (\x -> let y = f x in y `seq` (y, x))
 
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
@@ -7,11 +7,12 @@
     (|:), (|>), snoc,
     appendl, appendr,
     sortOn, union, unionBy,
+    nubOrd, nubOrdBy, nubOrdOn,
     maximum1, minimum1, maximumBy1, minimumBy1, maximumOn1, minimumOn1
     ) where
 
 import           Data.Function
-import qualified Data.List as List
+import qualified Data.List.Extra as List
 import           Data.List.NonEmpty
 
 #if __GLASGOW_HASKELL__ <= 802
@@ -61,6 +62,25 @@
 -- > (1 :| [3, 5, 3]) `union` (4 :| [5, 3, 5, 2]) == 1 :| [3, 5, 3, 4, 2]
 union :: Eq a => NonEmpty a -> NonEmpty a -> NonEmpty a
 union = unionBy (==)
+
+-- | @nubOrd@ for 'NonEmpty'. Behaves the same as 'Data.List.Extra.nubOrd'.
+--
+-- > Data.List.NonEmpty.Extra.nubOrd (1 :| [2, 3, 3, 4, 1, 2]) == 1 :| [2, 3, 4]
+-- > \xs -> Data.List.NonEmpty.Extra.nubOrd xs == Data.List.NonEmpty.Extra.nub xs
+nubOrd :: Ord a => NonEmpty a -> NonEmpty a
+nubOrd = nubOrdBy compare
+
+-- | @nubOrdBy@ for 'NonEmpty'. Behaves the same as 'Data.List.Extra.nubOrdBy'.
+--
+-- > Data.List.NonEmpty.Extra.nubOrdBy (compare `on` Data.List.length) ("a" :| ["test","of","this"]) == "a" :| ["test","of"]
+nubOrdBy :: (a -> a -> Ordering) -> NonEmpty a -> NonEmpty a
+nubOrdBy cmp = fromList . List.nubOrdBy cmp . toList
+
+-- | @nubOrdOn@ for 'NonEmpty'. Behaves the same as 'Data.List.Extra.nubOrdOn'.
+--
+-- > Data.List.NonEmpty.Extra.nubOrdOn Data.List.length ("a" :| ["test","of","this"]) == "a" :| ["test","of"]
+nubOrdOn :: Ord b => (a -> b) -> NonEmpty a -> NonEmpty a
+nubOrdOn f = fromList . List.nubOrdOn f . toList
 
 -- | The non-overloaded version of 'union'.
 unionBy :: (a -> a -> Bool) -> NonEmpty a -> NonEmpty a -> NonEmpty a
diff --git a/src/Extra.hs b/src/Extra.hs
--- a/src/Extra.hs
+++ b/src/Extra.hs
@@ -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)
+import Data.List.NonEmpty.Extra hiding (cons, snoc, sortOn, union, unionBy, nubOrd, nubOrdBy, nubOrdOn)
 import Data.Tuple.Extra
 import Data.Version.Extra
 import Numeric.Extra
diff --git a/src/System/Directory/Extra.hs b/src/System/Directory/Extra.hs
--- a/src/System/Directory/Extra.hs
+++ b/src/System/Directory/Extra.hs
@@ -52,12 +52,12 @@
 --
 -- > withTempDir $ \dir -> do writeFile (dir </> "test.txt") ""; (== [dir </> "test.txt"]) <$> listContents dir
 -- > let touch = mapM_ $ \x -> createDirectoryIfMissing True (takeDirectory x) >> writeFile x ""
--- > let listTest op as bs = withTempDir $ \dir -> do touch $ map (dir </>) as; res <- op dir; return $ map (drop (length dir + 1)) res == bs
+-- > let listTest op as bs = withTempDir $ \dir -> do touch $ map (dir </>) as; res <- op dir; pure $ map (drop (length dir + 1)) res == bs
 -- > listTest listContents ["bar.txt","foo/baz.txt","zoo"] ["bar.txt","foo","zoo"]
 listContents :: FilePath -> IO [FilePath]
 listContents dir = do
     xs <- getDirectoryContents dir
-    return $ sort [dir </> x | x <- xs, not $ all (== '.') x]
+    pure $ sort [dir </> x | x <- xs, not $ all (== '.') x]
 
 
 -- | Like 'listContents', but only returns the directories in a directory, not the files.
@@ -81,21 +81,21 @@
 --
 -- > listTest listFilesRecursive ["bar.txt","zoo","foo" </> "baz.txt"] ["bar.txt","zoo","foo" </> "baz.txt"]
 listFilesRecursive :: FilePath -> IO [FilePath]
-listFilesRecursive = listFilesInside (const $ return True)
+listFilesRecursive = listFilesInside (const $ pure True)
 
 
 -- | Like 'listFilesRecursive', but with a predicate to decide where to recurse into.
 --   Typically directories starting with @.@ would be ignored. The initial argument directory
 --   will have the test applied to it.
 --
--- > listTest (listFilesInside $ return . not . isPrefixOf "." . takeFileName)
+-- > listTest (listFilesInside $ pure . not . isPrefixOf "." . takeFileName)
 -- >     ["bar.txt","foo" </> "baz.txt",".foo" </> "baz2.txt", "zoo"] ["bar.txt","zoo","foo" </> "baz.txt"]
--- > listTest (listFilesInside $ const $ return False) ["bar.txt"] []
+-- > listTest (listFilesInside $ const $ pure False) ["bar.txt"] []
 listFilesInside :: (FilePath -> IO Bool) -> FilePath -> IO [FilePath]
-listFilesInside test dir = ifM (notM $ test $ dropTrailingPathSeparator dir) (return []) $ do
+listFilesInside test dir = ifM (notM $ test $ dropTrailingPathSeparator dir) (pure []) $ do
     (dirs,files) <- partitionM doesDirectoryExist =<< listContents dir
     rest <- concatMapM (listFilesInside test) dirs
-    return $ files ++ rest
+    pure $ files ++ rest
 
 
 -- | Create a directory with permissions so that only the current user can view it.
diff --git a/src/System/IO/Extra.hs b/src/System/IO/Extra.hs
--- a/src/System/IO/Extra.hs
+++ b/src/System/IO/Extra.hs
@@ -66,7 +66,7 @@
 hGetContents' h = do
     s <- hGetContents h
     void $ evaluate $ length s
-    return s
+    pure s
 
 -- | A strict version of 'readFile'. When the string is produced, the entire
 --   file will have been read into memory and the file handle will have been closed.
@@ -112,7 +112,7 @@
 
 -- | Capture the 'stdout' and 'stderr' of a computation.
 --
--- > captureOutput (print 1) == return ("1\n",())
+-- > captureOutput (print 1) == pure ("1\n",())
 captureOutput :: IO a -> IO (String, a)
 captureOutput act = withTempFile $ \file ->
     withFile file ReadWriteMode $ \h -> do
@@ -120,7 +120,7 @@
             hClose h
             act
         out <- readFile' file
-        return (out, res)
+        pure (out, res)
     where
         clone out h act = do
             buf <- hGetBuffering out
@@ -166,13 +166,13 @@
 newTempFileWithin tmpdir = do
         file <- create
         del <- once $ ignore $ removeFile file
-        return (file, del)
+        pure (file, del)
     where
         create = do
             val <- tempUnique
             (file, h) <- retryBool (\(_ :: IOError) -> True) 5 $ openTempFile tmpdir $ "extra-file-" ++ show val ++ "-"
             hClose h
-            return file
+            pure file
 
 
 -- | Create a temporary file in the temporary directory. The file will be deleted
@@ -180,9 +180,9 @@
 --   The 'FilePath' will not have any file extension, will exist, and will be zero bytes long.
 --   If you require a file with a specific name, use 'withTempDir'.
 --
--- > withTempFile doesFileExist == return True
--- > (doesFileExist =<< withTempFile return) == return False
--- > withTempFile readFile' == return ""
+-- > withTempFile doesFileExist == pure True
+-- > (doesFileExist =<< withTempFile pure) == pure False
+-- > withTempFile readFile' == pure ""
 withTempFile :: (FilePath -> IO a) -> IO a
 withTempFile act = do
     (file, del) <- newTempFile
@@ -200,22 +200,22 @@
 newTempDirWithin tmpdir = do
         dir <- retryBool (\(_ :: IOError) -> True) 5 $ create tmpdir
         del <- once $ ignore $ removeDirectoryRecursive dir
-        return (dir, del)
+        pure (dir, del)
     where
         create tmpdir = do
             v <- tempUnique
             let dir = tmpdir </> "extra-dir-" ++ show v
             catchBool isAlreadyExistsError
-                (createDirectoryPrivate dir >> return dir) $
+                (createDirectoryPrivate dir >> pure dir) $
                 \_ -> create tmpdir
 
 
 -- | Create a temporary directory inside the system temporary directory.
 --   The directory will be deleted after the action completes.
 --
--- > withTempDir doesDirectoryExist == return True
--- > (doesDirectoryExist =<< withTempDir return) == return False
--- > withTempDir listFiles == return []
+-- > withTempDir doesDirectoryExist == pure True
+-- > (doesDirectoryExist =<< withTempDir pure) == pure False
+-- > withTempDir listFiles == pure []
 withTempDir :: (FilePath -> IO a) -> IO a
 withTempDir act = do
     (dir,del) <- newTempDir
@@ -235,8 +235,8 @@
             r1 <- hGetBuf h1 b1 bufsz
             r2 <- hGetBuf h2 b2 bufsz
             if r1 == 0
-                then return $ r2 == 0
-                else return (r1 == r2) &&^ bufeq b1 b2 r1 &&^ eq b1 b2
+                then pure $ r2 == 0
+                else pure (r1 == r2) &&^ bufeq b1 b2 r1 &&^ eq b1 b2
           bufeq b1 b2 s = (==0) <$> memcmp b1 b2 (fromIntegral s)
           withb = allocaBytesAligned bufsz 4096
           bufsz = 64*1024
diff --git a/src/System/Process/Extra.hs b/src/System/Process/Extra.hs
--- a/src/System/Process/Extra.hs
+++ b/src/System/Process/Extra.hs
@@ -40,4 +40,4 @@
     (res,out) <- systemOutput x
     when (res /= ExitSuccess) $
         error $ "Failed when running system command: " ++ x
-    return out
+    pure out
diff --git a/src/System/Time/Extra.hs b/src/System/Time/Extra.hs
--- a/src/System/Time/Extra.hs
+++ b/src/System/Time/Extra.hs
@@ -26,18 +26,18 @@
 
 -- | Sleep for a number of seconds.
 --
--- > fmap (round . fst) (duration $ sleep 1) == return 1
+-- > fmap (round . fst) (duration $ sleep 1) == pure 1
 sleep :: Seconds -> IO ()
 sleep = loopM $ \s ->
     -- important to handle both overflow and underflow vs Int
     if s < 0 then
-        return $ Right ()
+        pure $ Right ()
     else if s > 2000 then do
         threadDelay 2000000000 -- 2000 * 1e6
-        return $ Left $ s - 2000
+        pure $ Left $ s - 2000
     else do
         threadDelay $ ceiling $ s * 1000000
-        return $ Right ()
+        pure $ Right ()
 
 
 -- An internal type that is thrown as a dynamic exception to
@@ -52,19 +52,19 @@
 --   overflows the bounds of an 'Int'. In addition, the bug that negative
 --   timeouts run for ever has been fixed.
 --
--- > timeout (-3) (print 1) == return Nothing
+-- > timeout (-3) (print 1) == pure Nothing
 -- > timeout 0.1  (print 1) == fmap Just (print 1)
--- > do (t, _) <- duration $ timeout 0.1 $ sleep 1000; print t; return $ t < 1
--- > timeout 0.1  (sleep 2 >> print 1) == return Nothing
+-- > do (t, _) <- duration $ timeout 0.1 $ sleep 1000; print t; pure $ t < 1
+-- > timeout 0.1  (sleep 2 >> print 1) == pure Nothing
 timeout :: Seconds -> IO a -> IO (Maybe a)
 -- Copied from GHC with a few tweaks.
 timeout n f
-    | n <= 0 = return Nothing
+    | n <= 0 = pure Nothing
     | otherwise = do
         pid <- myThreadId
         ex  <- fmap Timeout newUnique
         handleBool (== ex)
-                   (const $ return Nothing)
+                   (const $ pure Nothing)
                    (bracket (forkIOWithUnmask $ \unmask -> unmask $ sleep n >> throwTo pid ex)
                             killThread
                             (\_ -> fmap Just f))
@@ -90,13 +90,13 @@
 -- | Call once to start, then call repeatedly to get the elapsed time since the first call.
 --   The time is guaranteed to be monotonic. This function is robust to system time changes.
 --
--- > do f <- offsetTime; xs <- replicateM 10 f; return $ xs == sort xs
+-- > do f <- offsetTime; xs <- replicateM 10 f; pure $ xs == sort xs
 offsetTime :: IO (IO Seconds)
 offsetTime = do
     start <- time
-    return $ do
+    pure $ do
         end <- time
-        return $ 1e-9 * fromIntegral (toNanoSecs $ end - start)
+        pure $ 1e-9 * fromIntegral (toNanoSecs $ end - start)
     where time = getTime Monotonic
 
 {-# DEPRECATED offsetTimeIncrease "Use 'offsetTime' instead, which is guaranteed to always increase." #-}
@@ -107,10 +107,10 @@
 
 -- | Record how long a computation takes in 'Seconds'.
 --
--- > do (a,_) <- duration $ sleep 1; return $ a >= 1 && a <= 1.5
+-- > do (a,_) <- duration $ sleep 1; pure $ a >= 1 && a <= 1.5
 duration :: IO a -> IO (Seconds, a)
 duration act = do
     time <- offsetTime
     res <- act
     time <- time
-    return (time, res)
+    pure (time, res)
diff --git a/test/TestCustom.hs b/test/TestCustom.hs
--- a/test/TestCustom.hs
+++ b/test/TestCustom.hs
@@ -16,14 +16,14 @@
 
     testRaw "withTempFile" $ do
         xs <- replicateM 4 $ onceFork $ do
-            replicateM_ 100 $ withTempFile (const $ return ())
+            replicateM_ 100 $ withTempFile (const $ pure ())
             putChar '.'
         sequence_ xs
         putStrLn "done"
 
     testRaw "withTempDir" $ do
         xs <- replicateM 4 $ onceFork $ do
-            replicateM_ 100 $ withTempDir (const $ return ())
+            replicateM_ 100 $ withTempDir (const $ pure ())
             putChar '.'
         sequence_ xs
         putStrLn "done"
diff --git a/test/TestGen.hs b/test/TestGen.hs
--- a/test/TestGen.hs
+++ b/test/TestGen.hs
@@ -4,30 +4,33 @@
 {-# LANGUAGE ExtendedDefaultRules, ScopedTypeVariables, ViewPatterns #-}
 module TestGen(tests) where
 import TestUtil
+import qualified Data.List
+import qualified Data.List.NonEmpty.Extra
+import Test.QuickCheck.Instances.Semigroup ()
 default(Maybe Bool,Int,Double,Maybe (Maybe Bool),Maybe (Maybe Char))
 tests :: IO ()
 tests = do
     let x ||| y = do t1 <- onceFork x; t2 <- onceFork y; t1; t2
-    testGen "\\(x :: IO Int) -> void (once x) == return ()" $ \(x :: IO Int) -> void (once x) == return ()
+    testGen "\\(x :: IO Int) -> void (once x) == pure ()" $ \(x :: IO Int) -> void (once x) == pure ()
     testGen "\\(x :: IO Int) -> join (once x) == x" $ \(x :: IO Int) -> join (once x) == x
     testGen "\\(x :: IO Int) -> (do y <- once x; y; y) == x" $ \(x :: IO Int) -> (do y <- once x; y; y) == x
     testGen "\\(x :: IO Int) -> (do y <- once x; y ||| y) == x" $ \(x :: IO Int) -> (do y <- once x; y ||| y) == x
     testGen "\\(x :: IO Int) -> join (onceFork x) == x" $ \(x :: IO Int) -> join (onceFork x) == x
     testGen "\\(x :: IO Int) -> (do a <- onceFork x; a; a) == x" $ \(x :: IO Int) -> (do a <- onceFork x; a; a) == x
-    testGen "stringException \"test\"                           == return \"test\"" $ stringException "test"                           == return "test"
-    testGen "stringException (\"test\" ++ undefined)            == return \"test<Exception>\"" $ stringException ("test" ++ undefined)            == return "test<Exception>"
-    testGen "stringException (\"test\" ++ undefined ++ \"hello\") == return \"test<Exception>\"" $ stringException ("test" ++ undefined ++ "hello") == return "test<Exception>"
-    testGen "stringException ['t','e','s','t',undefined]      == return \"test<Exception>\"" $ stringException ['t','e','s','t',undefined]      == return "test<Exception>"
+    testGen "stringException \"test\"                           == pure \"test\"" $ stringException "test"                           == pure "test"
+    testGen "stringException (\"test\" ++ undefined)            == pure \"test<Exception>\"" $ stringException ("test" ++ undefined)            == pure "test<Exception>"
+    testGen "stringException (\"test\" ++ undefined ++ \"hello\") == pure \"test<Exception>\"" $ stringException ("test" ++ undefined ++ "hello") == pure "test<Exception>"
+    testGen "stringException ['t','e','s','t',undefined]      == pure \"test<Exception>\"" $ stringException ['t','e','s','t',undefined]      == pure "test<Exception>"
     testGen "ignore (print 1)    == print 1" $ ignore (print 1)    == print 1
-    testGen "ignore (fail \"die\") == return ()" $ ignore (fail "die") == return ()
-    testGen "catch (errorIO \"Hello\") (\\(ErrorCall x) -> return x) == return \"Hello\"" $ catch (errorIO "Hello") (\(ErrorCall x) -> return x) == return "Hello"
+    testGen "ignore (fail \"die\") == pure ()" $ ignore (fail "die") == pure ()
+    testGen "catch (errorIO \"Hello\") (\\(ErrorCall x) -> pure x) == pure \"Hello\"" $ catch (errorIO "Hello") (\(ErrorCall x) -> pure x) == pure "Hello"
     testGen "seq (errorIO \"foo\") (print 1) == print 1" $ seq (errorIO "foo") (print 1) == print 1
     testGen "retry 1 (print \"x\")  == print \"x\"" $ retry 1 (print "x")  == print "x"
     testGen "retry 3 (fail \"die\") == fail \"die\"" $ retry 3 (fail "die") == fail "die"
-    testGen "whenJust Nothing  print == return ()" $ whenJust Nothing  print == return ()
+    testGen "whenJust Nothing  print == pure ()" $ whenJust Nothing  print == pure ()
     testGen "whenJust (Just 1) print == print 1" $ whenJust (Just 1) print == print 1
     testGen "whenMaybe True  (print 1) == fmap Just (print 1)" $ whenMaybe True  (print 1) == fmap Just (print 1)
-    testGen "whenMaybe False (print 1) == return Nothing" $ whenMaybe False (print 1) == return Nothing
+    testGen "whenMaybe False (print 1) == pure Nothing" $ whenMaybe False (print 1) == pure Nothing
     testGen "\\(x :: Maybe ()) -> unit x == x" $ \(x :: Maybe ()) -> unit x == x
     testGen "fold1M (\\x y -> Just x) [] == undefined" $ erroneous $ fold1M (\x y -> Just x) []
     testGen "fold1M (\\x y -> Just $ x + y) [1, 2, 3] == Just 6" $ fold1M (\x y -> Just $ x + y) [1, 2, 3] == Just 6
@@ -243,6 +246,10 @@
     testGen "appendl (1 :| [2,3]) [4,5] == 1 :| [2,3,4,5]" $ appendl (1 :| [2,3]) [4,5] == 1 :| [2,3,4,5]
     testGen "appendr [1,2,3] (4 :| [5]) == 1 :| [2,3,4,5]" $ appendr [1,2,3] (4 :| [5]) == 1 :| [2,3,4,5]
     testGen "(1 :| [3, 5, 3]) `union` (4 :| [5, 3, 5, 2]) == 1 :| [3, 5, 3, 4, 2]" $ (1 :| [3, 5, 3]) `union` (4 :| [5, 3, 5, 2]) == 1 :| [3, 5, 3, 4, 2]
+    testGen "Data.List.NonEmpty.Extra.nubOrd (1 :| [2, 3, 3, 4, 1, 2]) == 1 :| [2, 3, 4]" $ Data.List.NonEmpty.Extra.nubOrd (1 :| [2, 3, 3, 4, 1, 2]) == 1 :| [2, 3, 4]
+    testGen "\\xs -> Data.List.NonEmpty.Extra.nubOrd xs == Data.List.NonEmpty.Extra.nub xs" $ \xs -> Data.List.NonEmpty.Extra.nubOrd xs == Data.List.NonEmpty.Extra.nub xs
+    testGen "Data.List.NonEmpty.Extra.nubOrdBy (compare `on` Data.List.length) (\"a\" :| [\"test\",\"of\",\"this\"]) == \"a\" :| [\"test\",\"of\"]" $ Data.List.NonEmpty.Extra.nubOrdBy (compare `on` Data.List.length) ("a" :| ["test","of","this"]) == "a" :| ["test","of"]
+    testGen "Data.List.NonEmpty.Extra.nubOrdOn Data.List.length (\"a\" :| [\"test\",\"of\",\"this\"]) == \"a\" :| [\"test\",\"of\"]" $ Data.List.NonEmpty.Extra.nubOrdOn Data.List.length ("a" :| ["test","of","this"]) == "a" :| ["test","of"]
     testGen "first succ (1,\"test\") == (2,\"test\")" $ first succ (1,"test") == (2,"test")
     testGen "second reverse (1,\"test\") == (1,\"tset\")" $ second reverse (1,"test") == (1,"tset")
     testGen "firstM (\\x -> [x-1, x+1]) (1,\"test\") == [(0,\"test\"),(2,\"test\")]" $ firstM (\x -> [x-1, x+1]) (1,"test") == [(0,"test"),(2,"test")]
@@ -259,38 +266,38 @@
     testGen "withTempDir $ \\dir -> do writeFile (dir </> \"foo.txt\") \"\"; withCurrentDirectory dir $ doesFileExist \"foo.txt\"" $ withTempDir $ \dir -> do writeFile (dir </> "foo.txt") ""; withCurrentDirectory dir $ doesFileExist "foo.txt"
     testGen "withTempDir $ \\dir -> do writeFile (dir </> \"test.txt\") \"\"; (== [dir </> \"test.txt\"]) <$> listContents dir" $ withTempDir $ \dir -> do writeFile (dir </> "test.txt") ""; (== [dir </> "test.txt"]) <$> listContents dir
     let touch = mapM_ $ \x -> createDirectoryIfMissing True (takeDirectory x) >> writeFile x ""
-    let listTest op as bs = withTempDir $ \dir -> do touch $ map (dir </>) as; res <- op dir; return $ map (drop (length dir + 1)) res == bs
+    let listTest op as bs = withTempDir $ \dir -> do touch $ map (dir </>) as; res <- op dir; pure $ map (drop (length dir + 1)) res == bs
     testGen "listTest listContents [\"bar.txt\",\"foo/baz.txt\",\"zoo\"] [\"bar.txt\",\"foo\",\"zoo\"]" $ listTest listContents ["bar.txt","foo/baz.txt","zoo"] ["bar.txt","foo","zoo"]
     testGen "listTest listDirectories [\"bar.txt\",\"foo/baz.txt\",\"zoo\"] [\"foo\"]" $ listTest listDirectories ["bar.txt","foo/baz.txt","zoo"] ["foo"]
     testGen "listTest listFiles [\"bar.txt\",\"foo/baz.txt\",\"zoo\"] [\"bar.txt\",\"zoo\"]" $ listTest listFiles ["bar.txt","foo/baz.txt","zoo"] ["bar.txt","zoo"]
     testGen "listTest listFilesRecursive [\"bar.txt\",\"zoo\",\"foo\" </> \"baz.txt\"] [\"bar.txt\",\"zoo\",\"foo\" </> \"baz.txt\"]" $ listTest listFilesRecursive ["bar.txt","zoo","foo" </> "baz.txt"] ["bar.txt","zoo","foo" </> "baz.txt"]
-    testGen "listTest (listFilesInside $ return . not . isPrefixOf \".\" . takeFileName)    [\"bar.txt\",\"foo\" </> \"baz.txt\",\".foo\" </> \"baz2.txt\", \"zoo\"] [\"bar.txt\",\"zoo\",\"foo\" </> \"baz.txt\"]" $ listTest (listFilesInside $ return . not . isPrefixOf "." . takeFileName)    ["bar.txt","foo" </> "baz.txt",".foo" </> "baz2.txt", "zoo"] ["bar.txt","zoo","foo" </> "baz.txt"]
-    testGen "listTest (listFilesInside $ const $ return False) [\"bar.txt\"] []" $ listTest (listFilesInside $ const $ return False) ["bar.txt"] []
+    testGen "listTest (listFilesInside $ pure . not . isPrefixOf \".\" . takeFileName)    [\"bar.txt\",\"foo\" </> \"baz.txt\",\".foo\" </> \"baz2.txt\", \"zoo\"] [\"bar.txt\",\"zoo\",\"foo\" </> \"baz.txt\"]" $ listTest (listFilesInside $ pure . not . isPrefixOf "." . takeFileName)    ["bar.txt","foo" </> "baz.txt",".foo" </> "baz2.txt", "zoo"] ["bar.txt","zoo","foo" </> "baz.txt"]
+    testGen "listTest (listFilesInside $ const $ pure False) [\"bar.txt\"] []" $ listTest (listFilesInside $ const $ pure False) ["bar.txt"] []
     testGen "isWindows == (os == \"mingw32\")" $ isWindows == (os == "mingw32")
     testGen "\\(filter isHexDigit -> s) -> fmap (== s) $ withTempFile $ \\file -> do writeFile file s; readFile' file" $ \(filter isHexDigit -> s) -> fmap (== s) $ withTempFile $ \file -> do writeFile file s; readFile' file
     testGen "\\s -> withTempFile $ \\file -> do writeFileUTF8 file s; fmap (== s) $ readFileUTF8' file" $ \s -> withTempFile $ \file -> do writeFileUTF8 file s; fmap (== s) $ readFileUTF8' file
     testGen "\\(ASCIIString s) -> withTempFile $ \\file -> do writeFileBinary file s; fmap (== s) $ readFileBinary' file" $ \(ASCIIString s) -> withTempFile $ \file -> do writeFileBinary file s; fmap (== s) $ readFileBinary' file
-    testGen "captureOutput (print 1) == return (\"1\\n\",())" $ captureOutput (print 1) == return ("1\n",())
-    testGen "withTempFile doesFileExist == return True" $ withTempFile doesFileExist == return True
-    testGen "(doesFileExist =<< withTempFile return) == return False" $ (doesFileExist =<< withTempFile return) == return False
-    testGen "withTempFile readFile' == return \"\"" $ withTempFile readFile' == return ""
-    testGen "withTempDir doesDirectoryExist == return True" $ withTempDir doesDirectoryExist == return True
-    testGen "(doesDirectoryExist =<< withTempDir return) == return False" $ (doesDirectoryExist =<< withTempDir return) == return False
-    testGen "withTempDir listFiles == return []" $ withTempDir listFiles == return []
+    testGen "captureOutput (print 1) == pure (\"1\\n\",())" $ captureOutput (print 1) == pure ("1\n",())
+    testGen "withTempFile doesFileExist == pure True" $ withTempFile doesFileExist == pure True
+    testGen "(doesFileExist =<< withTempFile pure) == pure False" $ (doesFileExist =<< withTempFile pure) == pure False
+    testGen "withTempFile readFile' == pure \"\"" $ withTempFile readFile' == pure ""
+    testGen "withTempDir doesDirectoryExist == pure True" $ withTempDir doesDirectoryExist == pure True
+    testGen "(doesDirectoryExist =<< withTempDir pure) == pure False" $ (doesDirectoryExist =<< withTempDir pure) == pure False
+    testGen "withTempDir listFiles == pure []" $ withTempDir listFiles == pure []
     testGen "fileEq \"does_not_exist1\" \"does_not_exist2\" == undefined" $ erroneousIO $ fileEq "does_not_exist1" "does_not_exist2"
     testGen "fileEq \"does_not_exist\" \"does_not_exist\" == undefined" $ erroneousIO $ fileEq "does_not_exist" "does_not_exist"
     testGen "withTempFile $ \\f1 -> fileEq \"does_not_exist\" f1 == undefined" $ erroneousIO $ withTempFile $ \f1 -> fileEq "does_not_exist" f1
     testGen "withTempFile $ \\f1 -> withTempFile $ \\f2 -> fileEq f1 f2" $ withTempFile $ \f1 -> withTempFile $ \f2 -> fileEq f1 f2
     testGen "withTempFile $ \\f1 -> withTempFile $ \\f2 -> writeFile f1 \"a\" >> writeFile f2 \"a\" >> fileEq f1 f2" $ withTempFile $ \f1 -> withTempFile $ \f2 -> writeFile f1 "a" >> writeFile f2 "a" >> fileEq f1 f2
     testGen "withTempFile $ \\f1 -> withTempFile $ \\f2 -> writeFile f1 \"a\" >> writeFile f2 \"b\" >> notM (fileEq f1 f2)" $ withTempFile $ \f1 -> withTempFile $ \f2 -> writeFile f1 "a" >> writeFile f2 "b" >> notM (fileEq f1 f2)
-    testGen "fmap (round . fst) (duration $ sleep 1) == return 1" $ fmap (round . fst) (duration $ sleep 1) == return 1
-    testGen "timeout (-3) (print 1) == return Nothing" $ timeout (-3) (print 1) == return Nothing
+    testGen "fmap (round . fst) (duration $ sleep 1) == pure 1" $ fmap (round . fst) (duration $ sleep 1) == pure 1
+    testGen "timeout (-3) (print 1) == pure Nothing" $ timeout (-3) (print 1) == pure Nothing
     testGen "timeout 0.1  (print 1) == fmap Just (print 1)" $ timeout 0.1  (print 1) == fmap Just (print 1)
-    testGen "do (t, _) <- duration $ timeout 0.1 $ sleep 1000; print t; return $ t < 1" $ do (t, _) <- duration $ timeout 0.1 $ sleep 1000; print t; return $ t < 1
-    testGen "timeout 0.1  (sleep 2 >> print 1) == return Nothing" $ timeout 0.1  (sleep 2 >> print 1) == return Nothing
+    testGen "do (t, _) <- duration $ timeout 0.1 $ sleep 1000; print t; pure $ t < 1" $ do (t, _) <- duration $ timeout 0.1 $ sleep 1000; print t; pure $ t < 1
+    testGen "timeout 0.1  (sleep 2 >> print 1) == pure Nothing" $ timeout 0.1  (sleep 2 >> print 1) == pure Nothing
     testGen "showDuration 3.435   == \"3.44s\"" $ showDuration 3.435   == "3.44s"
     testGen "showDuration 623.8   == \"10m24s\"" $ showDuration 623.8   == "10m24s"
     testGen "showDuration 62003.8 == \"17h13m\"" $ showDuration 62003.8 == "17h13m"
     testGen "showDuration 1e8     == \"27777h47m\"" $ showDuration 1e8     == "27777h47m"
-    testGen "do f <- offsetTime; xs <- replicateM 10 f; return $ xs == sort xs" $ do f <- offsetTime; xs <- replicateM 10 f; return $ xs == sort xs
-    testGen "do (a,_) <- duration $ sleep 1; return $ a >= 1 && a <= 1.5" $ do (a,_) <- duration $ sleep 1; return $ a >= 1 && a <= 1.5
+    testGen "do f <- offsetTime; xs <- replicateM 10 f; pure $ xs == sort xs" $ do f <- offsetTime; xs <- replicateM 10 f; pure $ xs == sort xs
+    testGen "do (a,_) <- duration $ sleep 1; pure $ a >= 1 && a <= 1.5" $ do (a,_) <- duration $ sleep 1; pure $ a >= 1 && a <= 1.5
diff --git a/test/TestUtil.hs b/test/TestUtil.hs
--- a/test/TestUtil.hs
+++ b/test/TestUtil.hs
@@ -46,7 +46,7 @@
 testGen msg prop = testRaw msg $ do
     r <- quickCheckResult prop
     case r of
-        Success{} -> return ()
+        Success{} -> pure ()
         _ -> errorIO "Test failed"
 
 testRaw :: String -> IO () -> IO ()
@@ -87,7 +87,7 @@
     a == b = unsafePerformIO $ do
         a <- try_ $ captureOutput a
         b <- try_ $ captureOutput b
-        if a == b then return True else
+        if a == b then pure True else
             error $ show ("IO values not equal", a, b)
 
 instance Show (IO a) where
@@ -96,10 +96,10 @@
 instance Arbitrary a => Arbitrary (IO a) where
     arbitrary = do
         (prnt :: Maybe Int, thrw :: Maybe Int, res) <- arbitrary
-        return $ do
+        pure $ do
             whenJust prnt print
             whenJust thrw (fail . show)
-            return res
+            pure res
 
 instance Eq SomeException where
     a == b = show a == show b
