streamly-0.8.0: test/Streamly/Test/Data/Fold.hs
module Main (main) where
import Data.Semigroup (Sum(..), getSum)
import Streamly.Test.Common (checkListEqual, listEquals)
import Test.QuickCheck
( Gen
, Property
, arbitrary
, choose
, forAll
, listOf
, listOf1
, property
, vectorOf
, withMaxSuccess
)
import Test.QuickCheck.Monadic (monadicIO, assert, run)
import qualified Prelude
import qualified Streamly.Internal.Data.Fold as F
import qualified Streamly.Prelude as S
import qualified Streamly.Internal.Data.Stream.IsStream as Stream
import qualified Streamly.Data.Fold as FL
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 $ S.fold F.rollingHash $ S.take n $ S.fromList vec
b <- run $ S.fold (F.rollingHashFirstN n) $ S.fromList vec
assert $ a == b
head :: [Int] -> Expectation
head ls = S.fold FL.head (S.fromList ls) `shouldReturn` headl ls
headl :: [a] -> Maybe a
headl [] = Nothing
headl (x:_) = Just x
length :: [Int] -> Expectation
length ls = S.fold FL.length (S.fromList ls) `shouldReturn` Prelude.length ls
sum :: [Int] -> Expectation
sum ls = S.fold FL.sum (S.fromList ls) `shouldReturn` Prelude.sum ls
product :: [Int] -> Expectation
product ls =
S.fold FL.product (S.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 =
S.fold (FL.maximumBy f) (S.fromList ls)
`shouldReturn` foldMaybe (greater f) genmin ls
maximum :: (Show a, Ord a) => a -> [a] -> Expectation
maximum genmin ls =
S.fold FL.maximum (S.fromList ls)
`shouldReturn` foldMaybe (greater compare) genmin ls
minimumBy :: (Ord a, Show a) => a -> (a -> a -> Ordering) -> [a] -> Expectation
minimumBy genmax f ls =
S.fold (FL.minimumBy f) (S.fromList ls)
`shouldReturn` foldMaybe (lesser f) genmax ls
minimum :: (Show a, Ord a) => a -> [a] -> Expectation
minimum genmax ls =
S.fold FL.minimum (S.fromList ls)
`shouldReturn` foldMaybe (lesser compare) genmax ls
toList :: [Int] -> Expectation
toList ls = S.fold FL.toList (S.fromList ls) `shouldReturn` ls
safeLast :: [a] -> Maybe a
safeLast [] = Nothing
safeLast (x:[]) = Just x
safeLast (_:xs) = safeLast xs
last :: [String] -> Expectation
last ls = S.fold FL.last (S.fromList ls) `shouldReturn` safeLast ls
mapMaybe :: [Int] -> Expectation
mapMaybe ls =
let maybeEven x =
if even x
then Just x
else Nothing
f = F.mapMaybe maybeEven FL.toList
in S.fold f (S.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 = S.fold (FL.index idx) (S.fromList ls)
in x `shouldReturn` nth idx ls
find :: (Show a, Eq a) => (a -> Bool) -> [a] -> Expectation
find f ls = do
y <- S.fold (FL.findIndex f) (S.fromList ls)
case y of
Nothing ->
let fld = S.fold (FL.find f) (S.fromList ls)
in fld `shouldReturn` Nothing
Just idx ->
let fld = S.fold (FL.any f) (S.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 <- S.fold (FL.findIndex f) (S.fromList ls)
case y of
Nothing ->
let fld = S.fold (FL.all $ neg f) (S.fromList ls)
in fld `shouldReturn` True
Just idx ->
if idx == 0
then
S.fold (FL.all f) (S.fromList []) `shouldReturn` True
else
S.fold (FL.all f) (S.fromList $ Prelude.take idx ls)
`shouldReturn` False
predicate :: Int -> Bool
predicate x = x * x < 100
elemIndex :: Int -> [Int] -> Expectation
elemIndex elm ls = do
y <- S.fold (FL.elemIndex elm) (S.fromList ls)
case y of
Nothing ->
let fld = S.fold (FL.any (== elm)) (S.fromList ls)
in fld `shouldReturn` False
Just idx ->
let fld = S.fold (FL.any (== elm)) (S.fromList $ Prelude.take idx ls)
in fld `shouldReturn` False
null :: [Int] -> Expectation
null ls =
S.fold FL.null (S.fromList ls)
`shouldReturn`
case ls of
[] -> True
_ -> False
elem :: Int -> [Int] -> Expectation
elem elm ls = do
y <- S.fold (FL.elem elm) (S.fromList ls)
let fld = S.fold (FL.any (== elm)) (S.fromList ls)
fld `shouldReturn` y
notElem :: Int -> [Int] -> Expectation
notElem elm ls = do
y <- S.fold (FL.notElem elm) (S.fromList ls)
let fld = S.fold (FL.any (== elm)) (S.fromList ls)
fld `shouldReturn` not y
all :: (a -> Bool) -> [a] -> Expectation
all f ls =
S.fold (FL.all f) (S.fromList ls) `shouldReturn` Prelude.all f ls
any :: (a -> Bool) -> [a] -> Expectation
any f ls = S.fold (FL.any f) (S.fromList ls) `shouldReturn` Prelude.any f ls
and :: [Bool] -> Expectation
and ls = S.fold FL.and (S.fromList ls) `shouldReturn` Prelude.and ls
or :: [Bool] -> Expectation
or ls = S.fold FL.or (S.fromList ls) `shouldReturn` Prelude.or ls
take :: [Int] -> Property
take ls =
forAll (chooseInt (-1, Prelude.length ls + 2)) $ \n ->
S.fold (F.take n FL.toList) (S.fromList ls)
`shouldReturn` Prelude.take n ls
takeEndBy_ :: Property
takeEndBy_ =
forAll (listOf (chooseInt (0, 1))) $ \ls ->
let p = (== 1)
f = F.takeEndBy_ p FL.toList
ys = Prelude.takeWhile (not . p) ls
in case S.fold f (S.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 = F.takeEndBy_ p (F.take n FL.toList)
ys = Prelude.take n (Prelude.takeWhile (not . p) ls)
in case S.fold f (S.fromList ls) of
Right xs -> checkListEqual xs ys
Left _ -> property False
chooseFloat :: (Float, Float) -> Gen Float
chooseFloat = choose
drain :: [Int] -> Expectation
drain ls = S.fold FL.drain (S.fromList ls) `shouldReturn` ()
drainBy :: [Int] -> Expectation
drainBy ls = S.fold (FL.drainBy return) (S.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 $ S.fold FL.mean (S.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 $ S.fold FL.stdDev (S.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 $ S.fold FL.variance (S.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 $ S.fold FL.mconcat (S.map Sum $ S.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 $ S.fold (FL.foldMap Sum) $ S.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 $ S.fold (FL.foldMapM (return . Sum)) $ S.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 $ S.fold (FL.lookup key) $ S.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 = FL.rmapM addLen FL.sum
v2 = foldl (+) (Prelude.length ls) ls
v1 <- run $ S.fold fld $ S.fromList ls
assert (v1 == v2)
teeWithLength :: Property
teeWithLength =
forAll (listOf1 (chooseInt (intMin, intMax)))
$ \ls0 -> monadicIO $ action ls0
where
action ls = do
v1 <- run $ S.fold (FL.tee FL.sum FL.length) $ S.fromList ls
let v2 = Prelude.sum ls
v3 = Prelude.length ls
assert (v1 == (v2, v3))
teeWithMax :: Property
teeWithMax =
forAll (listOf1 (chooseInt (intMin, intMax)))
$ \ls0 -> monadicIO $ action ls0
where
action ls = do
v1 <- run $ S.fold (FL.tee FL.sum FL.maximum) $ S.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 $ S.fold (FL.distribute [FL.sum, FL.length]) $ S.fromList ls
let v2 = Prelude.sum ls
v3 = Prelude.length ls
assert (v1 == [v2, v3])
partition :: Property
partition =
monadicIO $ do
v1 :: (Int, [String]) <-
run
$ S.fold (FL.partition FL.sum FL.toList)
$ S.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
$ S.fold (FL.unzip FL.sum FL.toList)
$ S.fromList [(1, "aa"), (2, "bb"), (3, "cc")]
let v2 = (6, ["aa", "bb", "cc"])
assert (v1 == v2)
many :: Property
many =
forAll (listOf (chooseInt (0, 100))) $ \lst ->
forAll (chooseInt (1, 100)) $ \i ->
monadicIO $ do
let strm = S.fromList lst
r1 <- S.fold (F.many (split i) F.toList) strm
r2 <- S.toList $ Stream.foldMany (split i) strm
assert $ r1 == r2
where
split i = F.take i F.toList
headAndRest :: [Int] -> Property
headAndRest ls = monadicIO $ do
(mbh, rest) <- run $ Stream.fold_ FL.head (S.fromList ls)
rests <- run $ S.toList rest
assert (mbh == headl ls)
listEquals (==) rests (taill ls)
where
taill :: [a] -> [a]
taill [] = []
taill (_:xs) = xs
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
-- 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
-- Combinators
-- Transformation
-- 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
-- serialWith
-- Distributing
-- tee
prop "teeWithLength" Main.teeWithLength
prop "teeWithMax" Main.teeWithMax
prop "distribute" Main.distribute
-- Partitioning
prop "partition" Main.partition
-- Unzipping
prop "unzip" Main.unzip
-- Nesting
prop "many" Main.many
-- concatMap
-- chunksOf
prop "head from fold_" headAndRest