diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,9 @@
 Changelog for Extra
 
+0.4
+    Remove all but the extractors on triples
+    Remove groupSortOn
+    Remove dropAround
 0.3.2
     Remove use of ===, allows older QuickCheck versions
 0.3.1
diff --git a/Generate.hs b/Generate.hs
--- a/Generate.hs
+++ b/Generate.hs
@@ -61,8 +61,8 @@
 tweakTest x
     | Just x <- stripSuffix " == undefined" x =
         if not $ "\\" `isPrefixOf` x then
-            "erroneous $ " ++ x
+            "erroneous $ " ++ trim x
         else
-            let (a,b) = breakOn "->" x
+            let (a,b) = breakOn "->" $ trim x
             in a ++ "-> erroneous $ " ++ drop 2 b
     | otherwise = x
diff --git a/extra.cabal b/extra.cabal
--- a/extra.cabal
+++ b/extra.cabal
@@ -1,7 +1,7 @@
 cabal-version:      >= 1.10
 build-type:         Simple
 name:               extra
-version:            0.3.2
+version:            0.4
 license:            BSD3
 license-file:       LICENSE
 category:           Development
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
@@ -86,7 +86,7 @@
 
 newtype Lock = Lock (MVar ())
 
--- | Create a 'newLock'.
+-- | Create a new 'Lock'.
 newLock :: IO Lock
 newLock = fmap Lock $ newMVar ()
 
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
@@ -6,9 +6,9 @@
 --   Some of the names and semantics were inspired by the @text@ package.
 module Data.List.Extra(
     module Data.List,
-    lower, upper, trim, trimStart, trimEnd, dropAround, word1, drop1,
+    lower, upper, trim, trimStart, trimEnd, word1, drop1,
     list, uncons, unsnoc, cons, snoc,
-    groupSort, groupSortOn, nubOn, groupOn, sortOn,
+    groupSort, nubOn, groupOn, sortOn,
     repeatedly, for,
     disjoint, allSame, anySame,
     dropEnd, takeEnd, breakEnd, spanEnd, dropWhileEnd, dropWhileEnd', takeWhileEnd, stripSuffix,
@@ -18,8 +18,8 @@
     ) where
 
 import Data.List
+import Data.Maybe
 import Data.Function
-import Data.Ord
 import Data.Char
 import Data.Tuple.Extra
 
@@ -117,67 +117,122 @@
 snoc :: [a] -> a -> [a]
 snoc xs x = xs ++ [x]
 
+
+-- | Take a number of elements from the end of the list.
+--
+-- > takeEnd 3 "hello"  == "llo"
+-- > takeEnd 5 "bye"    == "bye"
+-- > takeEnd (-1) "bye" == ""
+-- > \i xs -> takeEnd i xs `isSuffixOf` xs
+-- > \i xs -> length (takeEnd i xs) == min (max 0 i) (length xs)
 takeEnd :: Int -> [a] -> [a]
-takeEnd i = reverse . take i . reverse
+takeEnd i xs = f xs (drop i xs)
+    where f (x:xs) (y:ys) = f xs ys
+          f xs _ = xs
 
+-- | Drop a number of elements from the end of the list.
+--
+-- > dropEnd 3 "hello"  == "he"
+-- > dropEnd 5 "bye"    == ""
+-- > dropEnd (-1) "bye" == "bye"
+-- > \i xs -> dropEnd i xs `isPrefixOf` xs
+-- > \i xs -> length (dropEnd i xs) == max 0 (length xs - max 0 i)
+-- > \i -> take 3 (dropEnd 5 [i..]) == take 3 [i..]
 dropEnd :: Int -> [a] -> [a]
-dropEnd i = reverse . drop i . reverse
+dropEnd i xs = f xs (drop i xs)
+    where f (x:xs) (y:ys) = x : f xs ys
+          f _ _ = []
 
+
+-- | A merging of 'unzip' and 'concat'.
+--
+-- > concatUnzip [("a","AB"),("bc","C")] == ("abc","ABC")
 concatUnzip :: [([a], [b])] -> ([a], [b])
 concatUnzip = (concat *** concat) . unzip
 
+-- | A merging of 'unzip3' and 'concat'.
+--
+-- > concatUnzip3 [("a","AB",""),("bc","C","123")] == ("abc","ABC","123")
 concatUnzip3 :: [([a],[b],[c])] -> ([a],[b],[c])
 concatUnzip3 xs = (concat a, concat b, concat c)
     where (a,b,c) = unzip3 xs
 
 
+-- | A version of 'takeWhile' operating from the end.
+--
+-- > takeWhileEnd even [2,3,4,6] == [4,6]
 takeWhileEnd :: (a -> Bool) -> [a] -> [a]
 takeWhileEnd f = reverse . takeWhile f . reverse
 
 
-trim, trimStart, trimEnd :: String -> String
+-- | Remove spaces from the start of a string, see 'trim'.
+trimStart :: String -> String
 trimStart = dropWhile isSpace
+
+-- | Remove spaces from the end of a string, see 'trim'.
+trimEnd :: String -> String
 trimEnd = dropWhileEnd isSpace
-trim = dropAround isSpace
 
--- | Documentation about lowercase
+-- | Remove spaces from either side of a string. A combination of 'trimEnd' and 'trimStart'.
 --
+-- > trim      "  hello   " == "hello"
+-- > trimStart "  hello   " == "hello   "
+-- > trimEnd   "  hello   " == "  hello"
+-- > \s -> trim s == trimEnd (trimStart s)
+trim :: String -> String
+trim = trimEnd . trimStart
+
+-- | Convert a string to lower case.
+--
 -- > lower "This is A TEST" == "this is a test"
 -- > lower "" == ""
 lower :: String -> String
 lower = map toLower
 
+-- | Convert a string to upper case.
+--
+-- > upper "This is A TEST" == "THIS IS A TEST"
+-- > upper "" == ""
 upper :: String -> String
 upper = map toUpper
 
-dropAround :: (a -> Bool) -> [a] -> [a]
-dropAround f = dropWhileEnd f . dropWhile f
 
-
 word1 :: String -> (String, String)
 word1 x = second (dropWhile isSpace) $ break isSpace $ dropWhile isSpace x
 
 
+-- | A version of 'sort' where the comparison is done on some extracted value.
+--
+-- > sortOn fst [(3,"z"),(1,""),(3,"a")] == [(1,""),(3,"z"),(3,"a")]
 sortOn :: Ord b => (a -> b) -> [a] -> [a]
-sortOn f = sortBy (comparing f)
+sortOn f = sortBy (compare `on` f)
 
+-- | A version of 'group' where the equality is done on some extracted value.
 groupOn :: Eq b => (a -> b) -> [a] -> [[a]]
 groupOn f = groupBy ((==) `on` f)
 
+-- | A version of 'nub' where the equality is done on some extracted value.
 nubOn :: Eq b => (a -> b) -> [a] -> [a]
 nubOn f = nubBy ((==) `on` f)
 
+-- | A combination of 'group' and 'sort'.
+--
+-- > groupSort [(1,'t'),(3,'t'),(2,'e'),(2,'s')] == [(1,"t"),(2,"es"),(3,"t")]
+-- > \xs -> map fst (groupSort xs) == sort (nub (map fst xs))
+-- > \xs -> concatMap snd (groupSort xs) == map snd (sortOn fst xs)
 groupSort :: Ord k => [(k, v)] -> [(k, [v])]
-groupSort = groupSortOn id
-
-groupSortOn :: Ord a => (k -> a) -> [(k, v)] -> [(k, [v])]
-groupSortOn f = map (\x -> (fst $ head x, map snd x)) . groupOn (f . fst) . sortOn (f . fst)
+groupSort = map (\x -> (fst $ head x, map snd x)) . groupOn fst . sortOn fst
 
 
+-- | Merge two lists which are assumed to be ordered.
+--
+-- > merge "ace" "bd" == "abcde"
+-- > \xs ys -> merge (sort xs) (sort ys) == sort (xs ++ ys)
 merge :: Ord a => [a] -> [a] -> [a]
 merge = mergeBy compare
 
 
+-- | Like 'merge', but with a custom ordering function.
 mergeBy :: (a -> a -> Ordering) -> [a] -> [a] -> [a]
 mergeBy f xs [] = xs
 mergeBy f [] ys = ys
@@ -185,7 +240,16 @@
     | f x y /= GT = x : mergeBy f xs (y:ys)
     | otherwise = y : mergeBy f (x:xs) ys
 
+
+-- | Replace a subsequence everywhere it occurs. The first argument must
+--   not be the empty list.
+--
+-- > replace "el" "_" "Hello Bella" == "H_lo B_la"
+-- > replace "el" "e" "Hello"       == "Helo"
+-- > replace "" "e" "Hello"         == undefined
+-- > \xs ys -> not (null xs) ==> replace xs xs ys == ys
 replace :: Eq a => [a] -> [a] -> [a] -> [a]
+replace [] _ _ = error "Extra.replace, first argument cannot be empty"
 replace from to xs | Just xs <- stripPrefix from xs = to ++ replace from to xs
 replace from to (x:xs) = x : replace from to xs
 replace from to [] = []
@@ -208,12 +272,23 @@
 spanEnd f = breakEnd (not . f)
 
 
+-- | A variant of 'words' with a custom test. In particular,
+--   adjacent separators are discarded, as are leading or trailing separators.
+--
+-- > wordsBy (== ':') "::xyz:abc::123::" == ["xyz","abc","123"]
+-- > \s -> wordsBy isSpace s == words s
 wordsBy :: (a -> Bool) -> [a] -> [[a]]
 wordsBy f s = case dropWhile f s of
     [] -> []
     x:xs -> (x:w) : wordsBy f (drop1 z)
         where (w,z) = break f xs
 
+-- | A variant of 'lines' with a custom test. In particular,
+--   if there is a trailing separator it will be discarded.
+--
+-- > linesBy (== ':') "::xyz:abc::123::" == ["","","xyz","abc","","123",""]
+-- > \s -> linesBy (== '\n') s == lines s
+-- > linesBy (== ';') "my;list;here;" == ["my","list","here"]
 linesBy :: (a -> Bool) -> [a] -> [[a]]
 linesBy f [] = []
 linesBy f s = cons $ case break f s of
@@ -223,10 +298,22 @@
   where
     cons ~(h, t) = h : t -- to fix a space leak, see the GHC defn of lines
 
+-- | Find the first element of a list for which the operation returns 'Just', along
+--   with the result of the operation. Like 'find' but useful where the function also
+--   computes some expensive information that can be reused. Particular useful
+--   when the function is monadic, see 'firstJustM'.
+--
+-- > firstJust id [Nothing,Just 3]  == Just 3
+-- > firstJust id [Nothing,Nothing] == Nothing
 firstJust :: (a -> Maybe b) -> [a] -> Maybe b
-firstJust p [] = Nothing
-firstJust p (x:xs) = maybe (firstJust p xs) Just (p x)
+firstJust f = listToMaybe . mapMaybe f
 
+
+-- | Equivalent to @drop 1@, but likely to be faster and a single lexeme.
+--
+-- > drop1 ""         == ""
+-- > drop1 "test"     == "est"
+-- > \xs -> drop 1 xs == drop1 xs
 drop1 :: [a] -> [a]
 drop1 [] = []
 drop1 (x:xs) = xs
@@ -237,13 +324,8 @@
 -- is the prefix of @haystack@ before @needle@ is matched.  The second
 -- is the remainder of @haystack@, starting with the match.
 --
--- Examples:
---
 -- > breakOn "::" "a::b::c" == ("a", "::b::c")
 -- > breakOn "/" "foobar"   == ("foobar", "")
---
--- Laws:
---
 -- > \needle haystack -> let (prefix,match) = breakOn needle haystack in prefix ++ match == haystack
 breakOn :: Eq a => [a] -> [a] -> ([a], [a])
 breakOn needle haystack | needle `isPrefixOf` haystack = ([], haystack)
@@ -266,15 +348,10 @@
 -- list argument, consuming the delimiter. An empty delimiter is
 -- invalid, and will cause an error to be raised.
 --
--- Examples:
---
 -- > splitOn "\r\n" "a\r\nb\r\nd\r\ne" == ["a","b","d","e"]
 -- > splitOn "aaa"  "aaaXaaaXaaaXaaa"  == ["","X","X","X",""]
 -- > splitOn "x"    "x"                == ["",""]
 -- > splitOn "x"    ""                 == [""]
---
--- and
---
 -- > \s x -> s /= "" ==> intercalate s (splitOn s x) == x
 -- > \c x -> splitOn [c] x                           == split (==c) x
 splitOn :: Eq a => [a] -> [a] -> [[a]]
@@ -287,10 +364,12 @@
 -- | Splits a list into components delimited by separators,
 -- where the predicate returns True for a separator element.  The
 -- resulting components do not contain the separators.  Two adjacent
--- separators result in an empty component in the output.  eg.
+-- separators result in an empty component in the output.
 --
--- > split (=='a') "aabbaca" == ["","","bb","c",""]
--- > split (=='a') ""        == [""]
+-- > split (== 'a') "aabbaca" == ["","","bb","c",""]
+-- > split (== 'a') ""        == [""]
+-- > split (== ':') "::xyz:abc::123::" == ["","","xyz","abc","","123","",""]
+-- > split (== ',') "my,list,here" == ["my","list","here"]
 split :: (a -> Bool) -> [a] -> [[a]]
 split f [] = [[]]
 split f (x:xs) | f x = [] : split f xs
@@ -304,7 +383,18 @@
 
 
 -- | A version of 'dropWhileEnd' but with different strictness properties.
---   Often outperforms if the list is short or the test is expensive.
+--   The function 'dropWhileEnd' can be used on an infinite list and tests the property
+--   on each character. In contrast, 'dropWhileEnd'' is strict in the spine of the list
+--   but only tests the trailing suffix.
+--   This version usually outperforms 'dropWhileEnd' if the list is short or the test is expensive.
+--   Note the tests below cover both the prime and non-prime variants.
+--
+-- > dropWhileEnd  isSpace "ab cde  " == "ab cde"
+-- > dropWhileEnd' isSpace "ab cde  " == "ab cde"
+-- > last (dropWhileEnd  even [undefined,3]) == undefined
+-- > last (dropWhileEnd' even [undefined,3]) == 3
+-- > head (dropWhileEnd  even (3:undefined)) == 3
+-- > head (dropWhileEnd' even (3:undefined)) == undefined
 dropWhileEnd' :: (a -> Bool) -> [a] -> [a]
 dropWhileEnd' p = foldr (\x xs -> if null xs && p x then [] else x : xs) []
 
diff --git a/src/Data/Tuple/Extra.hs b/src/Data/Tuple/Extra.hs
--- a/src/Data/Tuple/Extra.hs
+++ b/src/Data/Tuple/Extra.hs
@@ -8,8 +8,8 @@
     first, second, (***), (&&&),
     -- * More pair operations
     dupe, both,
-    -- * Operations on triples
-    fst3, snd3, thd3, first3, second3, third3, dupe3, both3,
+    -- * Extract from a triple
+    fst3, snd3, thd3
     ) where
 
 import Data.Tuple
@@ -18,48 +18,51 @@
 infixr 3 ***, &&&
 
 -- | Update the first component of a pair.
+--
+-- > first succ (1,"test") == (2,"test")
 first :: (a -> a') -> (a, b) -> (a', b)
 first = Arrow.first
 
 -- | Update the second component of a pair.
+--
+-- > second reverse (1,"test") == (1,"tset")
 second :: (b -> b') -> (a, b) -> (a, b')
 second = Arrow.second
 
+-- | Given two functions, apply one to the first component and one to the second.
+--   A specialised version of 'Control.Arrow.***'.
+--
+-- > (succ *** reverse) (1,"test") == (2,"tset")
 (***) :: (a -> a') -> (b -> b') -> (a, b) -> (a', b')
 (***) = (Arrow.***)
 
+-- | Given two functions, apply both to a single argument to form a pair.
+--   A specialised version of 'Control.Arrow.&&&'.
+--
+-- > (succ &&& pred) 1 == (2,0)
 (&&&) :: (a -> b) -> (a -> c) -> a -> (b, c)
 (&&&) = (Arrow.&&&)
 
+-- | Duplicate a single value into a pair.
+--
+-- > dupe 12 == (12, 12)
 dupe :: a -> (a,a)
 dupe x = (x,x)
 
+-- | Apply a single function to both componenets of a pair.
+--
+-- > both succ (1,2) == (2,3)
 both :: (a -> b) -> (a, a) -> (b, b)
 both f (x,y) = (f x, f y)
 
+-- | Extract the 'fst' of a triple.
 fst3 :: (a,b,c) -> a
 fst3 (a,b,c) = a
 
+-- | Extract the 'snd' of a triple.
 snd3 :: (a,b,c) -> b
 snd3 (a,b,c) = b
 
+-- | Extract the final element of a triple.
 thd3 :: (a,b,c) -> c
 thd3 (a,b,c) = c
-
--- | Update the first component of a triple.
-first3 :: (a -> a') -> (a, b, c) -> (a', b, c)
-first3 f (a,b,c) = (f a, b, c)
-
--- | Update the second component of a triple.
-second3 :: (b -> b') -> (a, b, c) -> (a, b', c)
-second3 f (a,b,c) = (a, f b, c)
-
--- | Update the third component of a triple.
-third3 :: (c -> c') -> (a, b, c) -> (a, b, c')
-third3 f (a,b,c) = (a, b, f c)
-
-dupe3 :: a -> (a,a,a)
-dupe3 x = (x,x,x)
-
-both3 :: (a -> b) -> (a, a, a) -> (b, b, b)
-both3 f (x,y,z) = (f x, f y, f z)
diff --git a/src/Extra.hs b/src/Extra.hs
--- a/src/Extra.hs
+++ b/src/Extra.hs
@@ -20,10 +20,10 @@
     modifyIORef', writeIORef', atomicModifyIORef', atomicWriteIORef, atomicWriteIORef',
     -- * Data.List.Extra
     -- | Extra functions available in @"Data.List.Extra"@.
-    lower, upper, trim, trimStart, trimEnd, dropAround, word1, drop1, list, uncons, unsnoc, cons, snoc, groupSort, groupSortOn, nubOn, groupOn, sortOn, repeatedly, for, disjoint, allSame, anySame, dropEnd, takeEnd, breakEnd, spanEnd, dropWhileEnd, dropWhileEnd', takeWhileEnd, stripSuffix, concatUnzip, concatUnzip3, merge, mergeBy, replace, wordsBy, linesBy, firstJust, breakOn, breakOnEnd, splitOn, split, chunksOf,
+    lower, upper, trim, trimStart, trimEnd, word1, drop1, list, uncons, unsnoc, cons, snoc, groupSort, nubOn, groupOn, sortOn, repeatedly, for, disjoint, allSame, anySame, dropEnd, takeEnd, breakEnd, spanEnd, dropWhileEnd, dropWhileEnd', takeWhileEnd, stripSuffix, concatUnzip, concatUnzip3, merge, mergeBy, replace, wordsBy, linesBy, firstJust, breakOn, breakOnEnd, splitOn, split, chunksOf,
     -- * Data.Tuple.Extra
     -- | Extra functions available in @"Data.Tuple.Extra"@.
-    first, second, (***), (&&&), dupe, both, fst3, snd3, thd3, first3, second3, third3, dupe3, both3,
+    first, second, (***), (&&&), dupe, both, fst3, snd3, thd3,
     -- * Numeric.Extra
     -- | Extra functions available in @"Numeric.Extra"@.
     showDP, intToDouble, intToFloat, floatToDouble, doubleToFloat,
diff --git a/src/Numeric/Extra.hs b/src/Numeric/Extra.hs
--- a/src/Numeric/Extra.hs
+++ b/src/Numeric/Extra.hs
@@ -11,7 +11,7 @@
 ---------------------------------------------------------------------
 -- Data.String
 
--- | Show a number to a number of decimal places.
+-- | Show a number to a fixed number of decimal places.
 --
 -- > showDP 4 pi == "3.1416"
 -- > showDP 0 pi == "3"
@@ -24,19 +24,19 @@
 ---------------------------------------------------------------------
 -- Numeric
 
--- | Specialised numeric conversion.
+-- | Specialised numeric conversion, type restricted version of 'fromIntegral'.
 intToDouble :: Int -> Double
 intToDouble = fromIntegral
 
--- | Specialised numeric conversion.
+-- | Specialised numeric conversion, type restricted version of 'fromIntegral'.
 intToFloat :: Int -> Float
 intToFloat = fromIntegral
 
--- | Specialised numeric conversion.
+-- | Specialised numeric conversion, type restricted version of 'realToFrac'.
 floatToDouble :: Float -> Double
 floatToDouble = realToFrac
 
--- | Specialised numeric conversion.
+-- | Specialised numeric conversion, type restricted version of 'realToFrac'.
 doubleToFloat :: Double -> Float
 doubleToFloat = realToFrac
 
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
@@ -11,6 +11,8 @@
 import System.Exit
 
 
+-- | A version of 'system' that also captures the output, both 'stdout' and 'stderr'.
+--   Returns a pair of the exit code and the output.
 systemOutput :: String -> IO (ExitCode, String)
 systemOutput x = withTempFile $ \file -> do
     exit <- withFile file WriteMode $ \h -> do
@@ -19,12 +21,15 @@
     fmap (exit,) $ readFile' file
 
 
+-- | A version of 'system' that throws an error if the 'ExitCode' is not 'ExitSuccess'.
 system_ :: String -> IO ()
 system_ x = do
     res <- system x
     when (res /= ExitSuccess) $
         error $ "Failed when running system command: " ++ x
 
+-- | A version of 'system' that captures the output (both 'stdout' and 'stderr')
+--   and throws an error if the 'ExitCode' is not 'ExitSuccess'.
 systemOutput_ :: String -> IO String
 systemOutput_ x = do
     (res,out) <- systemOutput x
diff --git a/test/TestGen.hs b/test/TestGen.hs
--- a/test/TestGen.hs
+++ b/test/TestGen.hs
@@ -72,14 +72,54 @@
     testGen "\\x xs -> uncons (cons x xs) == Just (x,xs)" $ \x xs -> uncons (cons x xs) == Just (x,xs)
     testGen "snoc \"tes\" 't' == \"test\"" $ snoc "tes" 't' == "test"
     testGen "\\xs x -> unsnoc (snoc xs x) == Just (xs,x)" $ \xs x -> unsnoc (snoc xs x) == Just (xs,x)
+    testGen "takeEnd 3 \"hello\"  == \"llo\"" $ takeEnd 3 "hello"  == "llo"
+    testGen "takeEnd 5 \"bye\"    == \"bye\"" $ takeEnd 5 "bye"    == "bye"
+    testGen "takeEnd (-1) \"bye\" == \"\"" $ takeEnd (-1) "bye" == ""
+    testGen "\\i xs -> takeEnd i xs `isSuffixOf` xs" $ \i xs -> takeEnd i xs `isSuffixOf` xs
+    testGen "\\i xs -> length (takeEnd i xs) == min (max 0 i) (length xs)" $ \i xs -> length (takeEnd i xs) == min (max 0 i) (length xs)
+    testGen "dropEnd 3 \"hello\"  == \"he\"" $ dropEnd 3 "hello"  == "he"
+    testGen "dropEnd 5 \"bye\"    == \"\"" $ dropEnd 5 "bye"    == ""
+    testGen "dropEnd (-1) \"bye\" == \"bye\"" $ dropEnd (-1) "bye" == "bye"
+    testGen "\\i xs -> dropEnd i xs `isPrefixOf` xs" $ \i xs -> dropEnd i xs `isPrefixOf` xs
+    testGen "\\i xs -> length (dropEnd i xs) == max 0 (length xs - max 0 i)" $ \i xs -> length (dropEnd i xs) == max 0 (length xs - max 0 i)
+    testGen "\\i -> take 3 (dropEnd 5 [i..]) == take 3 [i..]" $ \i -> take 3 (dropEnd 5 [i..]) == take 3 [i..]
+    testGen "concatUnzip [(\"a\",\"AB\"),(\"bc\",\"C\")] == (\"abc\",\"ABC\")" $ concatUnzip [("a","AB"),("bc","C")] == ("abc","ABC")
+    testGen "concatUnzip3 [(\"a\",\"AB\",\"\"),(\"bc\",\"C\",\"123\")] == (\"abc\",\"ABC\",\"123\")" $ concatUnzip3 [("a","AB",""),("bc","C","123")] == ("abc","ABC","123")
+    testGen "takeWhileEnd even [2,3,4,6] == [4,6]" $ takeWhileEnd even [2,3,4,6] == [4,6]
+    testGen "trim      \"  hello   \" == \"hello\"" $ trim      "  hello   " == "hello"
+    testGen "trimStart \"  hello   \" == \"hello   \"" $ trimStart "  hello   " == "hello   "
+    testGen "trimEnd   \"  hello   \" == \"  hello\"" $ trimEnd   "  hello   " == "  hello"
+    testGen "\\s -> trim s == trimEnd (trimStart s)" $ \s -> trim s == trimEnd (trimStart s)
     testGen "lower \"This is A TEST\" == \"this is a test\"" $ lower "This is A TEST" == "this is a test"
     testGen "lower \"\" == \"\"" $ lower "" == ""
+    testGen "upper \"This is A TEST\" == \"THIS IS A TEST\"" $ upper "This is A TEST" == "THIS IS A TEST"
+    testGen "upper \"\" == \"\"" $ upper "" == ""
+    testGen "sortOn fst [(3,\"z\"),(1,\"\"),(3,\"a\")] == [(1,\"\"),(3,\"z\"),(3,\"a\")]" $ sortOn fst [(3,"z"),(1,""),(3,"a")] == [(1,""),(3,"z"),(3,"a")]
+    testGen "groupSort [(1,'t'),(3,'t'),(2,'e'),(2,'s')] == [(1,\"t\"),(2,\"es\"),(3,\"t\")]" $ groupSort [(1,'t'),(3,'t'),(2,'e'),(2,'s')] == [(1,"t"),(2,"es"),(3,"t")]
+    testGen "\\xs -> map fst (groupSort xs) == sort (nub (map fst xs))" $ \xs -> map fst (groupSort xs) == sort (nub (map fst xs))
+    testGen "\\xs -> concatMap snd (groupSort xs) == map snd (sortOn fst xs)" $ \xs -> concatMap snd (groupSort xs) == map snd (sortOn fst xs)
+    testGen "merge \"ace\" \"bd\" == \"abcde\"" $ merge "ace" "bd" == "abcde"
+    testGen "\\xs ys -> merge (sort xs) (sort ys) == sort (xs ++ ys)" $ \xs ys -> merge (sort xs) (sort ys) == sort (xs ++ ys)
+    testGen "replace \"el\" \"_\" \"Hello Bella\" == \"H_lo B_la\"" $ replace "el" "_" "Hello Bella" == "H_lo B_la"
+    testGen "replace \"el\" \"e\" \"Hello\"       == \"Helo\"" $ replace "el" "e" "Hello"       == "Helo"
+    testGen "replace \"\" \"e\" \"Hello\"         == undefined" $ erroneous $ replace "" "e" "Hello"
+    testGen "\\xs ys -> not (null xs) ==> replace xs xs ys == ys" $ \xs ys -> not (null xs) ==> replace xs xs ys == ys
     testGen "breakEnd isLower \"youRE\" == (\"you\",\"RE\")" $ breakEnd isLower "youRE" == ("you","RE")
     testGen "breakEnd isLower \"youre\" == (\"youre\",\"\")" $ breakEnd isLower "youre" == ("youre","")
     testGen "breakEnd isLower \"YOURE\" == (\"\",\"YOURE\")" $ breakEnd isLower "YOURE" == ("","YOURE")
     testGen "spanEnd isUpper \"youRE\" == (\"you\",\"RE\")" $ spanEnd isUpper "youRE" == ("you","RE")
     testGen "spanEnd (not . isSpace) \"x y z\" == (\"x y \",\"z\")" $ spanEnd (not . isSpace) "x y z" == ("x y ","z")
     testGen "\\f xs-> spanEnd f xs == swap (both reverse (span f (reverse xs)))" $ \f xs-> spanEnd f xs == swap (both reverse (span f (reverse xs)))
+    testGen "wordsBy (== ':') \"::xyz:abc::123::\" == [\"xyz\",\"abc\",\"123\"]" $ wordsBy (== ':') "::xyz:abc::123::" == ["xyz","abc","123"]
+    testGen "\\s -> wordsBy isSpace s == words s" $ \s -> wordsBy isSpace s == words s
+    testGen "linesBy (== ':') \"::xyz:abc::123::\" == [\"\",\"\",\"xyz\",\"abc\",\"\",\"123\",\"\"]" $ linesBy (== ':') "::xyz:abc::123::" == ["","","xyz","abc","","123",""]
+    testGen "\\s -> linesBy (== '\\n') s == lines s" $ \s -> linesBy (== '\n') s == lines s
+    testGen "linesBy (== ';') \"my;list;here;\" == [\"my\",\"list\",\"here\"]" $ linesBy (== ';') "my;list;here;" == ["my","list","here"]
+    testGen "firstJust id [Nothing,Just 3]  == Just 3" $ firstJust id [Nothing,Just 3]  == Just 3
+    testGen "firstJust id [Nothing,Nothing] == Nothing" $ firstJust id [Nothing,Nothing] == Nothing
+    testGen "drop1 \"\"         == \"\"" $ drop1 ""         == ""
+    testGen "drop1 \"test\"     == \"est\"" $ drop1 "test"     == "est"
+    testGen "\\xs -> drop 1 xs == drop1 xs" $ \xs -> drop 1 xs == drop1 xs
     testGen "breakOn \"::\" \"a::b::c\" == (\"a\", \"::b::c\")" $ breakOn "::" "a::b::c" == ("a", "::b::c")
     testGen "breakOn \"/\" \"foobar\"   == (\"foobar\", \"\")" $ breakOn "/" "foobar"   == ("foobar", "")
     testGen "\\needle haystack -> let (prefix,match) = breakOn needle haystack in prefix ++ match == haystack" $ \needle haystack -> let (prefix,match) = breakOn needle haystack in prefix ++ match == haystack
@@ -90,15 +130,29 @@
     testGen "splitOn \"x\"    \"\"                 == [\"\"]" $ splitOn "x"    ""                 == [""]
     testGen "\\s x -> s /= \"\" ==> intercalate s (splitOn s x) == x" $ \s x -> s /= "" ==> intercalate s (splitOn s x) == x
     testGen "\\c x -> splitOn [c] x                           == split (==c) x" $ \c x -> splitOn [c] x                           == split (==c) x
-    testGen "split (=='a') \"aabbaca\" == [\"\",\"\",\"bb\",\"c\",\"\"]" $ split (=='a') "aabbaca" == ["","","bb","c",""]
-    testGen "split (=='a') \"\"        == [\"\"]" $ split (=='a') ""        == [""]
+    testGen "split (== 'a') \"aabbaca\" == [\"\",\"\",\"bb\",\"c\",\"\"]" $ split (== 'a') "aabbaca" == ["","","bb","c",""]
+    testGen "split (== 'a') \"\"        == [\"\"]" $ split (== 'a') ""        == [""]
+    testGen "split (== ':') \"::xyz:abc::123::\" == [\"\",\"\",\"xyz\",\"abc\",\"\",\"123\",\"\",\"\"]" $ split (== ':') "::xyz:abc::123::" == ["","","xyz","abc","","123","",""]
+    testGen "split (== ',') \"my,list,here\" == [\"my\",\"list\",\"here\"]" $ split (== ',') "my,list,here" == ["my","list","here"]
+    testGen "dropWhileEnd  isSpace \"ab cde  \" == \"ab cde\"" $ dropWhileEnd  isSpace "ab cde  " == "ab cde"
+    testGen "dropWhileEnd' isSpace \"ab cde  \" == \"ab cde\"" $ dropWhileEnd' isSpace "ab cde  " == "ab cde"
+    testGen "last (dropWhileEnd  even [undefined,3]) == undefined" $ erroneous $ last (dropWhileEnd  even [undefined,3])
+    testGen "last (dropWhileEnd' even [undefined,3]) == 3" $ last (dropWhileEnd' even [undefined,3]) == 3
+    testGen "head (dropWhileEnd  even (3:undefined)) == 3" $ head (dropWhileEnd  even (3:undefined)) == 3
+    testGen "head (dropWhileEnd' even (3:undefined)) == undefined" $ erroneous $ head (dropWhileEnd' even (3:undefined))
     testGen "stripSuffix \"bar\" \"foobar\" == Just \"foo\"" $ stripSuffix "bar" "foobar" == Just "foo"
     testGen "stripSuffix \"\"    \"baz\"    == Just \"baz\"" $ stripSuffix ""    "baz"    == Just "baz"
     testGen "stripSuffix \"foo\" \"quux\"   == Nothing" $ stripSuffix "foo" "quux"   == Nothing
     testGen "chunksOf 3 \"my test\" == [\"my \",\"tes\",\"t\"]" $ chunksOf 3 "my test" == ["my ","tes","t"]
     testGen "chunksOf 3 \"mytest\"  == [\"myt\",\"est\"]" $ chunksOf 3 "mytest"  == ["myt","est"]
     testGen "chunksOf 8 \"\"        == []" $ chunksOf 8 ""        == []
-    testGen "chunksOf 0 \"test\"    == undefined" $ erroneous $ chunksOf 0 "test"   
+    testGen "chunksOf 0 \"test\"    == undefined" $ erroneous $ chunksOf 0 "test"
+    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 "(succ *** reverse) (1,\"test\") == (2,\"tset\")" $ (succ *** reverse) (1,"test") == (2,"tset")
+    testGen "(succ &&& pred) 1 == (2,0)" $ (succ &&& pred) 1 == (2,0)
+    testGen "dupe 12 == (12, 12)" $ dupe 12 == (12, 12)
+    testGen "both succ (1,2) == (2,3)" $ both succ (1,2) == (2,3)
     testGen "showDP 4 pi == \"3.1416\"" $ showDP 4 pi == "3.1416"
     testGen "showDP 0 pi == \"3\"" $ showDP 0 pi == "3"
     testGen "showDP 2 3  == \"3.00\"" $ showDP 2 3  == "3.00"
