packages feed

extra 0.3.2 → 0.4

raw patch · 10 files changed

+222/−66 lines, 10 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Data.List.Extra: dropAround :: (a -> Bool) -> [a] -> [a]
- Data.List.Extra: groupSortOn :: Ord a => (k -> a) -> [(k, v)] -> [(k, [v])]
- Data.Tuple.Extra: both3 :: (a -> b) -> (a, a, a) -> (b, b, b)
- Data.Tuple.Extra: dupe3 :: a -> (a, a, a)
- Data.Tuple.Extra: first3 :: (a -> a') -> (a, b, c) -> (a', b, c)
- Data.Tuple.Extra: second3 :: (b -> b') -> (a, b, c) -> (a, b', c)
- Data.Tuple.Extra: third3 :: (c -> c') -> (a, b, c) -> (a, b, c')
- Extra: both3 :: (a -> b) -> (a, a, a) -> (b, b, b)
- Extra: dropAround :: (a -> Bool) -> [a] -> [a]
- Extra: dupe3 :: a -> (a, a, a)
- Extra: first3 :: (a -> a') -> (a, b, c) -> (a', b, c)
- Extra: groupSortOn :: Ord a => (k -> a) -> [(k, v)] -> [(k, [v])]
- Extra: second3 :: (b -> b') -> (a, b, c) -> (a, b', c)
- Extra: third3 :: (c -> c') -> (a, b, c) -> (a, b, c')

Files

CHANGES.txt view
@@ -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
Generate.hs view
@@ -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
extra.cabal view
@@ -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
src/Control/Concurrent/Extra.hs view
@@ -86,7 +86,7 @@  newtype Lock = Lock (MVar ()) --- | Create a 'newLock'.+-- | Create a new 'Lock'. newLock :: IO Lock newLock = fmap Lock $ newMVar () 
src/Data/List/Extra.hs view
@@ -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) [] 
src/Data/Tuple/Extra.hs view
@@ -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)
src/Extra.hs view
@@ -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,
src/Numeric/Extra.hs view
@@ -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 
src/System/Process/Extra.hs view
@@ -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
test/TestGen.hs view
@@ -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"