{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-} module Main (main) where import Data.List (sort) import Data.Semigroup (Sum(..), getSum) import Streamly.Test.Common (checkListEqual, listEquals) import Test.QuickCheck ( Gen , Property , arbitrary , choose , forAll , listOf , listOf1 , property , vectorOf , withMaxSuccess , generate ) import Control.Monad.IO.Class (liftIO) import Test.QuickCheck.Monadic (monadicIO, assert, run) import qualified Data.Map import qualified Prelude import qualified Streamly.Internal.Data.Array.Mut as MArray import qualified Streamly.Internal.Data.Fold as Fold import qualified Streamly.Internal.Data.Fold.Container as Fold import qualified Streamly.Internal.Data.Stream as Stream import Prelude hiding (maximum, minimum, elem, notElem, null, product, sum, head, last, take) import Test.Hspec as H import Test.Hspec.QuickCheck maxStreamLen :: Int maxStreamLen = 1000 intMin :: Int intMin = minBound intMax :: Int intMax = maxBound min_value :: Int min_value = 0 max_value :: Int max_value = 10000 chooseInt :: (Int, Int) -> Gen Int chooseInt = choose {-# INLINE maxStreamLen #-} {-# INLINE intMin #-} {-# INLINE intMax #-} rollingHashFirstN :: Property rollingHashFirstN = forAll (choose (0, maxStreamLen)) $ \len -> forAll (choose (0, len)) $ \n -> forAll (vectorOf len (arbitrary :: Gen Int)) $ \vec -> monadicIO $ do a <- run $ Stream.fold Fold.rollingHash $ Stream.take n $ Stream.fromList vec b <- run $ Stream.fold (Fold.rollingHashFirstN n) $ Stream.fromList vec assert $ a == b head :: [Int] -> Expectation head ls = Stream.fold Fold.one (Stream.fromList ls) `shouldReturn` headl ls headl :: [a] -> Maybe a headl [] = Nothing headl (x:_) = Just x length :: [Int] -> Expectation length ls = Stream.fold Fold.length (Stream.fromList ls) `shouldReturn` Prelude.length ls sum :: [Int] -> Expectation sum ls = Stream.fold Fold.sum (Stream.fromList ls) `shouldReturn` Prelude.sum ls product :: [Int] -> Expectation product ls = Stream.fold Fold.product (Stream.fromList ls) `shouldReturn` Prelude.product ls lesser :: (a -> a -> Ordering) -> a -> a -> a lesser f x y = if f x y == LT then x else y greater :: (a -> a -> Ordering) -> a -> a -> a greater f x y = if f x y == GT then x else y foldMaybe :: (b -> a -> b) -> b -> [a] -> Maybe b foldMaybe f acc ls = case ls of [] -> Nothing _ -> Just (foldl f acc ls) maximumBy :: (Ord a, Show a) => a -> (a -> a -> Ordering) -> [a] -> Expectation maximumBy genmin f ls = Stream.fold (Fold.maximumBy f) (Stream.fromList ls) `shouldReturn` foldMaybe (greater f) genmin ls maximum :: (Show a, Ord a) => a -> [a] -> Expectation maximum genmin ls = Stream.fold Fold.maximum (Stream.fromList ls) `shouldReturn` foldMaybe (greater compare) genmin ls minimumBy :: (Ord a, Show a) => a -> (a -> a -> Ordering) -> [a] -> Expectation minimumBy genmax f ls = Stream.fold (Fold.minimumBy f) (Stream.fromList ls) `shouldReturn` foldMaybe (lesser f) genmax ls minimum :: (Show a, Ord a) => a -> [a] -> Expectation minimum genmax ls = Stream.fold Fold.minimum (Stream.fromList ls) `shouldReturn` foldMaybe (lesser compare) genmax ls toList :: [Int] -> Expectation toList ls = Stream.fold Fold.toList (Stream.fromList ls) `shouldReturn` ls toListRev :: [Int] -> Expectation toListRev ls = Stream.fold Fold.toListRev (Stream.fromList ls) `shouldReturn` reverse ls safeLast :: [a] -> Maybe a safeLast [] = Nothing safeLast (x:[]) = Just x safeLast (_:xs) = safeLast xs last :: [String] -> Expectation last ls = Stream.fold Fold.last (Stream.fromList ls) `shouldReturn` safeLast ls mapMaybe :: [Int] -> Expectation mapMaybe ls = let maybeEven x = if even x then Just x else Nothing f = Fold.mapMaybe maybeEven Fold.toList in Stream.fold f (Stream.fromList ls) `shouldReturn` filter even ls nth :: Int -> [a] -> Maybe a nth idx (x : xs) | idx == 0 = Just x | idx < 0 = Nothing | otherwise = nth (idx - 1) xs nth _ [] = Nothing index :: Int -> [String] -> Expectation index idx ls = let x = Stream.fold (Fold.index idx) (Stream.fromList ls) in x `shouldReturn` nth idx ls find :: (Show a, Eq a) => (a -> Bool) -> [a] -> Expectation find f ls = do y <- Stream.fold (Fold.findIndex f) (Stream.fromList ls) case y of Nothing -> let fld = Stream.fold (Fold.find f) (Stream.fromList ls) in fld `shouldReturn` Nothing Just idx -> let fld = Stream.fold (Fold.any f) (Stream.fromList $ Prelude.take idx ls) in fld `shouldReturn` False neg :: (a -> Bool) -> a -> Bool neg f x = not (f x) findIndex :: (a -> Bool) -> [a] -> Expectation findIndex f ls = do y <- Stream.fold (Fold.findIndex f) (Stream.fromList ls) case y of Nothing -> let fld = Stream.fold (Fold.all $ neg f) (Stream.fromList ls) in fld `shouldReturn` True Just idx -> if idx == 0 then Stream.fold (Fold.all f) (Stream.fromList []) `shouldReturn` True else Stream.fold (Fold.all f) (Stream.fromList $ Prelude.take idx ls) `shouldReturn` False predicate :: Int -> Bool predicate x = x * x < 100 elemIndex :: Int -> [Int] -> Expectation elemIndex elm ls = do y <- Stream.fold (Fold.elemIndex elm) (Stream.fromList ls) case y of Nothing -> let fld = Stream.fold (Fold.any (== elm)) (Stream.fromList ls) in fld `shouldReturn` False Just idx -> let fld = Stream.fold (Fold.any (== elm)) (Stream.fromList $ Prelude.take idx ls) in fld `shouldReturn` False null :: [Int] -> Expectation null ls = Stream.fold Fold.null (Stream.fromList ls) `shouldReturn` case ls of [] -> True _ -> False elem :: Int -> [Int] -> Expectation elem elm ls = do y <- Stream.fold (Fold.elem elm) (Stream.fromList ls) let fld = Stream.fold (Fold.any (== elm)) (Stream.fromList ls) fld `shouldReturn` y notElem :: Int -> [Int] -> Expectation notElem elm ls = do y <- Stream.fold (Fold.notElem elm) (Stream.fromList ls) let fld = Stream.fold (Fold.any (== elm)) (Stream.fromList ls) fld `shouldReturn` not y all :: (a -> Bool) -> [a] -> Expectation all f ls = Stream.fold (Fold.all f) (Stream.fromList ls) `shouldReturn` Prelude.all f ls any :: (a -> Bool) -> [a] -> Expectation any f ls = Stream.fold (Fold.any f) (Stream.fromList ls) `shouldReturn` Prelude.any f ls and :: [Bool] -> Expectation and ls = Stream.fold Fold.and (Stream.fromList ls) `shouldReturn` Prelude.and ls or :: [Bool] -> Expectation or ls = Stream.fold Fold.or (Stream.fromList ls) `shouldReturn` Prelude.or ls take :: [Int] -> Property take ls = forAll (chooseInt (-1, Prelude.length ls + 2)) $ \n -> Stream.fold (Fold.take n Fold.toList) (Stream.fromList ls) `shouldReturn` Prelude.take n ls takeEndBy_ :: Property takeEndBy_ = forAll (listOf (chooseInt (0, 1))) $ \ls -> let p = (== 1) f = Fold.takeEndBy_ p Fold.toList ys = Prelude.takeWhile (not . p) ls in case Stream.fold f (Stream.fromList ls) of Right xs -> checkListEqual xs ys Left _ -> property False takeEndByOrMax :: Property takeEndByOrMax = forAll (chooseInt (min_value, max_value)) $ \n -> forAll (listOf (chooseInt (0, 1))) $ \ls -> let p = (== 1) f = Fold.takeEndBy_ p (Fold.take n Fold.toList) ys = Prelude.take n (Prelude.takeWhile (not . p) ls) in case Stream.fold f (Stream.fromList ls) of Right xs -> checkListEqual xs ys Left _ -> property False chooseFloat :: (Float, Float) -> Gen Float chooseFloat = choose drain :: [Int] -> Expectation drain ls = Stream.fold Fold.drain (Stream.fromList ls) `shouldReturn` () drainBy :: [Int] -> Expectation drainBy ls = Stream.fold (Fold.drainBy return) (Stream.fromList ls) `shouldReturn` () mean :: Property mean = forAll (listOf1 (chooseFloat (-100.0, 100.0))) $ \ls0 -> withMaxSuccess 1000 $ monadicIO $ action ls0 where action ls = do v1 <- run $ Stream.fold Fold.mean (Stream.fromList ls) let v2 = Prelude.sum ls / fromIntegral (Prelude.length ls) assert (abs (v1 - v2) < 0.0001) stdDev :: Property stdDev = forAll (listOf1 (chooseFloat (-100.0, 100.0))) $ \ls0 -> withMaxSuccess 1000 $ monadicIO $ action ls0 where action ls = do v1 <- run $ Stream.fold Fold.stdDev (Stream.fromList ls) let avg = Prelude.sum ls / fromIntegral (Prelude.length ls) se = Prelude.sum (fmap (\x -> (x - avg) * (x - avg)) ls) sd = sqrt $ se / fromIntegral (Prelude.length ls) assert (abs (v1 - sd) < 0.0001 ) variance :: Property variance = forAll (listOf1 (chooseFloat (-100.0, 100.0))) $ \ls0 -> withMaxSuccess 1000 $ monadicIO $ action ls0 where action ls = do v1 <- run $ Stream.fold Fold.variance (Stream.fromList ls) let avg = Prelude.sum ls / fromIntegral (Prelude.length ls) se = Prelude.sum (fmap (\x -> (x - avg) * (x - avg)) ls) vr = se / fromIntegral (Prelude.length ls) assert (abs (v1 - vr) < 0.01 ) mconcat :: Property mconcat = forAll (listOf1 (chooseInt (intMin, intMax))) $ \ls0 -> monadicIO $ action ls0 where action ls = do v1 <- run $ Stream.fold Fold.mconcat (fmap Sum $ Stream.fromList ls) let v2 = Prelude.sum ls assert (getSum v1 == v2) foldMap :: Property foldMap = forAll (listOf1 (chooseInt (intMin, intMax))) $ \ls0 -> monadicIO $ action ls0 where action ls = do v1 <- run $ Stream.fold (Fold.foldMap Sum) $ Stream.fromList ls let v2 = Prelude.sum ls assert (getSum v1 == v2) foldMapM :: Property foldMapM = forAll (listOf1 (chooseInt (intMin, intMax))) $ \ls0 -> monadicIO $ action ls0 where action ls = do v1 <- run $ Stream.fold (Fold.foldMapM (return . Sum)) $ Stream.fromList ls let v2 = Prelude.sum ls assert (getSum v1 == v2) lookup :: Property lookup = forAll (chooseInt (1, 15)) $ \key0 ->monadicIO $ action key0 where action key = do let ls = [ (1, "first"), (2, "second"), (3, "third"), (4, "fourth") , (5, "fifth"), (6, "fifth+first"), (7, "fifth+second") , (8, "fifth+third"), (9, "fifth+fourth") , (10, "fifth+fifth") ] v1 <- run $ Stream.fold (Fold.lookup key) $ Stream.fromList ls let v2 = Prelude.lookup key ls assert (v1 == v2) rmapM :: Property rmapM = forAll (listOf1 (chooseInt (intMin, intMax))) $ \ls0 -> monadicIO $ action ls0 where action ls = do let addLen x = return $ x + Prelude.length ls fld = Fold.rmapM addLen Fold.sum v2 = foldl (+) (Prelude.length ls) ls v1 <- run $ Stream.fold fld $ Stream.fromList ls assert (v1 == v2) teeWithLength :: Property teeWithLength = forAll (listOf1 (chooseInt (intMin, intMax))) $ \ls0 -> monadicIO $ action ls0 where action ls = do v1 <- run $ Stream.fold (Fold.tee Fold.sum Fold.length) $ Stream.fromList ls let v2 = Prelude.sum ls v3 = Prelude.length ls assert (v1 == (v2, v3)) teeWithFstLength :: Property teeWithFstLength = forAll (listOf1 (chooseInt (intMin, intMax))) $ \ls0 -> monadicIO $ action ls0 where action ls = do v1 <- run $ Stream.fold (Fold.teeWithFst (,) (Fold.take 5 Fold.sum) Fold.length) $ Stream.fromList ls let v2 = Prelude.sum (Prelude.take 5 ls) v3 = Prelude.length (Prelude.take 5 ls) assert (v1 == (v2, v3)) partitionByM :: Property partitionByM = forAll (listOf1 (chooseInt (intMin, intMax))) $ \ls0 -> monadicIO $ action ls0 where action ls = do let f = \x -> if odd x then return (Left x) else return (Right x) v1 <- run $ Stream.fold (Fold.partitionByM f Fold.length Fold.length) $ Stream.fromList ls let v2 = foldl (\b a -> if odd a then b+1 else b) 0 ls v3 = foldl (\b a -> if even a then b+1 else b) 0 ls assert (v1 == (v2, v3)) partitionByFstM :: Property partitionByFstM = forAll (listOf1 (chooseInt (intMin, intMax))) $ \ls0 -> monadicIO $ action ls0 where action _ = do let f = \x -> if odd x then return (Left x) else return (Right x) v1 <- run $ Stream.fold (Fold.partitionByFstM f (Fold.take 25 Fold.length) Fold.length) (Stream.fromList ([1 .. 100] :: [Int])) let v2 = foldl (\b a -> if odd a then b+1 else b) 0 ([1..49] :: [Int]) v3 = foldl (\b a -> if even a then b+1 else b) 0 ([1..49] :: [Int]) assert (v1 == (v2, v3)) partitionByMinM1 :: Property partitionByMinM1 = forAll (listOf1 (chooseInt (intMin, intMax))) $ \ls0 -> monadicIO $ action ls0 where action _ = do let f = \x -> if odd x then return (Left x) else return (Right x) v1 <- run $ Stream.fold (Fold.partitionByMinM f Fold.length (Fold.take 25 Fold.length)) (Stream.fromList ([1 .. 100] :: [Int])) let v2 = foldl (\b a -> if odd a then b+1 else b) 0 ([1..50] :: [Int]) v3 = foldl (\b a -> if even a then b+1 else b) 0 ([1..50] :: [Int]) assert (v1 == (v2, v3)) partitionByMinM2 :: Property partitionByMinM2 = forAll (listOf1 (chooseInt (intMin, intMax))) $ \ls0 -> monadicIO $ action ls0 where action _ = do let f = \x -> if odd x then return (Left x) else return (Right x) v1 <- run $ Stream.fold (Fold.partitionByMinM f (Fold.take 25 Fold.length) Fold.length) (Stream.fromList ([1 .. 100] :: [Int])) let v2 = foldl (\b a -> if odd a then b+1 else b) 0 ([1..49] :: [Int]) v3 = foldl (\b a -> if even a then b+1 else b) 0 ([1..49] :: [Int]) assert (v1 == (v2, v3)) teeWithMinLength1 :: Property teeWithMinLength1 = forAll (listOf1 (chooseInt (intMin, intMax))) $ \ls0 -> monadicIO $ action ls0 where action ls = do v1 <- run $ Stream.fold (Fold.teeWithMin (,) (Fold.take 5 Fold.sum) Fold.length) $ Stream.fromList ls let v2 = Prelude.sum (Prelude.take 5 ls) v3 = Prelude.length (Prelude.take 5 ls) assert (v1 == (v2, v3)) teeWithMinLength2 :: Property teeWithMinLength2 = forAll (listOf1 (chooseInt (intMin, intMax))) $ \ls0 -> monadicIO $ action ls0 where action ls = do v1 <- run $ Stream.fold (Fold.teeWithMin (,) Fold.sum (Fold.take 5 Fold.length)) $ Stream.fromList ls let v2 = Prelude.sum (Prelude.take 5 ls) v3 = Prelude.length (Prelude.take 5 ls) assert (v1 == (v2, v3)) teeWithMax :: Property teeWithMax = forAll (listOf1 (chooseInt (intMin, intMax))) $ \ls0 -> monadicIO $ action ls0 where action ls = do v1 <- run $ Stream.fold (Fold.tee Fold.sum Fold.maximum) $ Stream.fromList ls let v2 = Prelude.sum ls v3 = foldMaybe (greater compare) intMin ls assert (v1 == (v2, v3)) distribute :: Property distribute = forAll (listOf1 (chooseInt (intMin, intMax))) $ \ls0 -> monadicIO $ action ls0 where action ls = do v1 <- run $ Stream.fold (Fold.distribute [Fold.sum, Fold.length]) $ Stream.fromList ls let v2 = Prelude.sum ls v3 = Prelude.length ls assert (v1 == [v2, v3]) partition :: Property partition = monadicIO $ do v1 :: (Int, [String]) <- run $ Stream.fold (Fold.partition Fold.sum Fold.toList) $ Stream.fromList [Left 1, Right "abc", Left 3, Right "xy", Right "pp2"] let v2 = (4,["abc","xy","pp2"]) assert (v1 == v2) unzip :: Property unzip = monadicIO $ do v1 :: (Int, [String]) <- run $ Stream.fold (Fold.unzip Fold.sum Fold.toList) $ Stream.fromList [(1, "aa"), (2, "bb"), (3, "cc")] let v2 = (6, ["aa", "bb", "cc"]) assert (v1 == v2) postscan :: Property postscan = forAll (listOf (chooseInt (intMin, intMax))) $ \ls -> monadicIO $ do v1 :: [Int] <- run $ Stream.fold (Fold.postscan Fold.sum Fold.toList) $ Stream.fromList ls let v2 = scanl1 (+) ls assert (v1 == v2) many :: Property many = forAll (listOf (chooseInt (0, 100))) $ \lst -> forAll (chooseInt (1, 100)) $ \i -> monadicIO $ do let strm = Stream.fromList lst r1 <- Stream.fold (Fold.many (split i) Fold.toList) strm r2 <- Stream.fold Fold.toList $ Stream.foldMany (split i) strm assert $ r1 == r2 where split i = Fold.take i Fold.toList foldBreak :: [Int] -> Property foldBreak ls = monadicIO $ do (mbh, rest) <- run $ Stream.foldBreak Fold.one (Stream.fromList ls) rests <- run $ Stream.fold Fold.toList rest assert (mbh == headl ls) listEquals (==) rests (taill ls) where taill :: [a] -> [a] taill [] = [] taill (_:xs) = xs demux :: Expectation demux = let table "SUM" = return Fold.sum table "PRODUCT" = return Fold.product table _ = return Fold.length input = Stream.fromList ( [ ("SUM", 1) , ("abc", 1) , ("PRODUCT", 2) , ("abc", 2) , ("SUM",3) , ("xyz", 1) , ("PRODUCT", 4) , ("xyz", 2) , ("abc", 2) ] :: [(String, Int)]) in Stream.fold (Fold.demuxKvToMap table) input `shouldReturn` Data.Map.fromList [("PRODUCT", 8),("SUM", 4),("abc",3),("xyz",2)] demuxWith :: Expectation demuxWith = let getKey x | even x = "SUM" | otherwise = "PRODUCT" getFold "SUM" = return Fold.sum getFold "PRODUCT" = return Fold.product getFold _ = error "demuxWith: bug" input = Stream.fromList [1, 2, 3, 4 :: Int] in Stream.fold (Fold.demuxToContainer getKey (getFold . getKey)) input `shouldReturn` Data.Map.fromList [("PRODUCT",3),("SUM",6)] classifyWith :: Expectation classifyWith = let input = Stream.fromList [("ONE",1),("ONE",1.1),("TWO",2), ("TWO",2.2)] in Stream.fold (Fold.toContainer fst (Fold.lmap snd Fold.toList)) input `shouldReturn` Data.Map.fromList [("ONE",[1.0, 1.1 :: Double]), ("TWO",[2.0, 2.2])] classify :: Expectation classify = let input = Stream.fromList [ ("ONE", (1::Int, 1)) , ("ONE", (1, 1.1:: Double)) , ("TWO", (2, 2)) , ("TWO",(2, 2.2)) ] in Stream.fold (Fold.kvToMap (Fold.lmap snd Fold.toList)) input `shouldReturn` Data.Map.fromList [("ONE",[1.0, 1.1 :: Double]), ("TWO",[2.0, 2.2])] splitAt :: Expectation splitAt = Stream.fold (Fold.splitAt 6 Fold.toList Fold.toList) (Stream.fromList "Hello World!") `shouldReturn` ("Hello ","World!") scan :: Property scan = forAll (listOf (chooseInt (0, 100))) $ \lst -> monadicIO $ do v1 :: [Int] <- run $ Stream.fold (Fold.scan Fold.sum Fold.toList) $ Stream.fromList lst let v2 = scanl (+) 0 lst assert (v1 == v2) topBy :: Bool -> Property topBy isTop = forAll (listOf (chooseInt (-50, 100))) $ \ls0 -> monadicIO $ action ls0 where action ls = do let n0 = Prelude.length ls n <- liftIO $ generate $ chooseInt (-2, n0 + 2) if isTop then do lst <- Stream.fold (Fold.top n) (Stream.fromList ls) >>= MArray.toList assert ((Prelude.take n . Prelude.reverse . sort) ls == lst) else do lst <- Stream.fold (Fold.bottom n) (Stream.fromList ls) >>= MArray.toList assert ((Prelude.take n . sort) ls == lst) top :: Property top = topBy True bottom :: Property bottom = topBy False nub :: Property nub = monadicIO $ do vals <- Stream.fold Fold.toList $ Stream.catMaybes $ Stream.postscan Fold.nub $ Stream.fromList [1::Int, 1, 2, 3, 4, 4, 5, 1, 5, 7] assert (vals == [1, 2, 3, 4, 5, 7]) moduleName :: String moduleName = "Data.Fold" main :: IO () main = hspec $ do describe moduleName $ do -- Folds -- Accumulators prop "mconcat" Main.mconcat prop "foldMap" Main.foldMap prop "foldMapM" Main.foldMapM prop "drain" Main.drain prop "drainBy" Main.drainBy prop "last" last prop "length" Main.length prop "sum" sum prop "product" product prop "maximumBy" $ maximumBy intMin compare prop "maximum" $ maximum intMin prop "minimumBy" $ minimumBy intMax compare prop "minimum" $ minimum intMax prop "mean" Main.mean prop "stdDev" Main.stdDev prop "variance" Main.variance prop "rollingHashFirstN" rollingHashFirstN prop "toList" toList prop "toListRev" toListRev prop "demux" demux prop "demuxWith" demuxWith prop "classifyWith" classifyWith prop "classify" classify -- Terminating folds prop "index" index prop "head" head prop "find" $ find predicate prop "lookup" Main.lookup prop "findIndex" $ findIndex predicate prop "elemIndex" $ elemIndex 10 prop "null" null prop "elem" $ elem 10 prop "notElem" $ notElem 10 prop "all" $ Main.all predicate prop "any" $ Main.any predicate prop "and" Main.and prop "or" Main.or prop "top" Main.top prop "bottom" Main.bottom prop "nub" Main.nub -- Combinators -- Transformation prop "scan" scan prop "postscan" Main.postscan -- rsequence -- Functor instance prop "rmapM" Main.rmapM -- lmap/lmapM -- Filtering -- filter/filterM -- catMaybes prop "mapMaybe" mapMaybe -- Trimming prop "take" take -- takeEndBy prop "takeEndBy_" takeEndBy_ prop "takeEndByOrMax" takeEndByOrMax -- Appending -- splitWith -- Distributing -- tee prop "teeWithLength" Main.teeWithLength prop "teeWithFstLength" Main.teeWithFstLength prop "teeWithMinLength1" Main.teeWithMinLength1 prop "teeWithMinLength2" Main.teeWithMinLength2 prop "teeWithMax" Main.teeWithMax prop "partitionByM" Main.partitionByM prop "partitionByFstM" Main.partitionByFstM prop "partitionByMinM1" Main.partitionByMinM1 prop "partitionByMinM2" Main.partitionByMinM2 prop "distribute" Main.distribute -- Partitioning prop "partition" Main.partition prop "partitionByM" partitionByM -- Unzipping prop "unzip" Main.unzip prop "splitAt" Main.splitAt -- Nesting prop "many" Main.many -- concatMap -- chunksOf prop "foldBreak" foldBreak