{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE QuantifiedConstraints #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE LambdaCase #-} module Hedgehog.Classes.Foldable (foldableLaws) where import Hedgehog import Hedgehog.Classes.Common import Control.Monad.IO.Class (MonadIO(..)) import Control.Exception (ErrorCall(..), try, evaluate) import Data.Monoid (Sum(..), Endo(..), Dual(..)) import qualified Data.Foldable as Foldable -- | Tests the following 'Foldable' laws: -- -- [__Fold__]: @'Foldable.fold' ≡ 'Foldable.foldMap' 'id'@ -- [__FoldMap__]: @'Foldable.foldMap' f ≡ 'Foldable.foldr' ('mappend' '.' f) 'mempty'@ -- [__Foldr__]: @'Foldable.foldr' f z t ≡ 'appEndo' ('Foldable.foldMap' ('Endo' '.' f) t) z@ -- [__Foldr'__]: @'Foldable.foldr'' f z0 t ≡ 'Foldable.foldl' f' 'id' t z0, where f' k x z = k '$!' f x z@ -- [__Foldl__]: @'Foldable.foldl' f z t ≡ 'appEndo' ('getDual' ('Foldable.foldMap' ('Dual' '.' 'Endo' '.' 'flip' f) t)) z@ -- [__Foldl'__]: @'Foldable.foldl'' f z0 xs ≡ 'Foldable.foldr' f' 'id' xs z0, where f' x k z = k '$!' f z x@ -- [__Foldl1__]: @'Foldable.foldl1' f t ≡ let (x:xs) = 'Foldable.toList' t in 'foldl' f x xs@ -- [__Foldr1__]: @'Foldable.foldr1' f t ≡ let (xs,x)@ = @unsnoc ('Foldable.toList' t) in 'foldr' f x xs@ -- [__ToList__]: @'Foldable.toList' ≡ 'Foldable.foldr' (:) []@ -- [__Null__]: @'Foldable.null' ≡ 'Foldable.foldr' ('const' ('const' 'False')) 'True'@ -- [__Length__]: @'Foldable.length' ≡ 'getSum' '.' 'Foldable.foldMap' ('const' ('Sum' 1))@ -- -- This additionally tests that the user's implementations of 'Foldable.foldr'' and 'Foldable.foldl'' are strict in their accumulators. foldableLaws :: ( Foldable f , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x) ) => (forall x. Gen x -> Gen (f x)) -> Laws foldableLaws gen = Laws "Foldable" [ ("fold", foldableFold gen) , ("foldMap", foldableFoldMap gen) , ("foldr", foldableFoldr gen) , ("foldr'", foldableFoldr' gen) , ("foldl", foldableFoldl gen) , ("foldl'", foldableFoldl' gen) , ("foldl1", foldableFoldl1 gen) , ("foldr1", foldableFoldr1 gen) , ("toList", foldableToList gen) , ("null", foldableNull gen) , ("length", foldableLength gen) ] foldableFold :: ( Foldable f , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x) ) => (forall x. Gen x -> Gen (f x)) -> Property foldableFold fgen = property $ do a <- forAll $ fgen $ genVerySmallList genSmallInteger let lhs = Foldable.fold a let rhs = Foldable.foldMap id a let ctx = contextualise $ LawContext { lawContextLawName = "Fold" , lawContextLawBody = "fold" `congruency` "foldMap id" , lawContextTcName = "Foldable" , lawContextTcProp = let showA = show a in lawWhere [ "fold a" `congruency` "foldMap id a, where" , "a = " ++ showA ] , lawContextReduced = reduced lhs rhs } heqCtx lhs rhs ctx foldableFoldMap :: ( Foldable f , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x) ) => (forall x. Gen x -> Gen (f x)) -> Property foldableFoldMap fgen = property $ do a <- forAll $ fgen genSmallInteger e <- forAll genQuadraticEquation let f = (:[]) . runQuadraticEquation e let lhs = Foldable.foldMap f a let rhs = Foldable.foldr (mappend . f) mempty a let ctx = contextualise $ LawContext { lawContextLawName = "FoldMap" , lawContextLawBody = "foldMap f" `congruency` "foldr (mappend . f) mempty" , lawContextTcName = "Foldable" , lawContextTcProp = let showA = show a showF = "(:[]) $ " ++ show e in lawWhere [ "foldMap f a" `congruency` "foldr (mappend . f) mempty a, where" , "f = " ++ showF , "a = " ++ showA ] , lawContextReduced = reduced lhs rhs } heqCtx lhs rhs ctx foldableFoldr :: ( Foldable f , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x) ) => (forall x. Gen x -> Gen (f x)) -> Property foldableFoldr fgen = property $ do e <- forAll genLinearEquationTwo z <- forAll genSmallInteger t <- forAll $ fgen genSmallInteger let f = runLinearEquationTwo e let lhs = Foldable.foldr f z t let rhs = appEndo (Foldable.foldMap (Endo . f) t) z let ctx = contextualise $ LawContext { lawContextLawName = "Foldr" , lawContextLawBody = "foldr f z t" `congruency` "appEndo (foldMap (Endo . f) t) z" , lawContextTcName = "Foldable" , lawContextTcProp = let showT = show t showF = show e showZ = show z in lawWhere [ "foldr f z t" `congruency` "appEndo (foldMap (Endo . f) t) z" , "f = " ++ showF , "z = " ++ showZ , "t = " ++ showT ] , lawContextReduced = reduced lhs rhs } heqCtx lhs rhs ctx foldableFoldl :: ( Foldable f , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x) ) => (forall x. Gen x -> Gen (f x)) -> Property foldableFoldl fgen = property $ do e <- forAll genLinearEquationTwo z <- forAll genSmallInteger t <- forAll $ fgen genSmallInteger let f = runLinearEquationTwo e let lhs = Foldable.foldl f z t let rhs = appEndo (getDual (Foldable.foldMap (Dual . Endo . flip f) t)) z let ctx = contextualise $ LawContext { lawContextLawName = "Foldl" , lawContextLawBody = "foldl f z t" `congruency` "appEndo (getDual (foldMap (Dual . Endo . flip f) t)) z" , lawContextTcName = "Foldable" , lawContextTcProp = let showT = show t showF = show e showZ = show z in lawWhere [ "foldl f z t" `congruency` "appEndo (getDual (foldMap (Dual . Endo . flip f) t)) z" , "f = " ++ showF , "z = " ++ showZ , "t = " ++ showT ] , lawContextReduced = reduced lhs rhs } heqCtx lhs rhs ctx ctxNotStrict :: String -> Context ctxNotStrict str = Context $ "Your implementation of " ++ str ++ " is not strict." foldableFoldr' :: ( Foldable f , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x) ) => (forall x. Gen x -> Gen (f x)) -> Property foldableFoldr' fgen = property $ do xs <- forAll $ fgen (genBottom genSmallInteger) let f :: Bottom Integer -> Integer -> Integer f a b = case a of BottomUndefined -> error "foldableFoldr': your foldr' is not strict!" BottomValue v -> if even v then v else b z0 <- forAll genSmallInteger (rhs, ctx1) <- liftIO $ do let f' k x z = k $! f x z e <- try (evaluate (Foldable.foldl f' id xs z0)) case e of Left (_ :: ErrorCall) -> pure (Nothing, ctxNotStrict "foldr'") Right i -> pure (Just i, NoContext) (lhs, ctx2) <- liftIO $ do e <- try (evaluate (Foldable.foldr' f z0 xs)) case e of Left (_ :: ErrorCall) -> pure (Nothing, ctxNotStrict "foldr'") Right i -> pure (Just i, NoContext) let ctx = case ctx1 of NoContext -> case ctx2 of NoContext -> contextualise $ LawContext { lawContextLawName = "Foldr'" , lawContextLawBody = "foldr' f z0 t" `congruency` "foldl f' id t z0, where f' k x z = k $! f x z" , lawContextTcName = "Foldable" , lawContextTcProp = let showT = show xs showF = "\\a b -> case a of\n BottomUndefined -> error \"foldableFoldr': not strict\"\n BottomValue v -> if even v then v else b" showZ = show z0 in lawWhere [ "foldr' f z0 t" `congruency` "foldl f' id t z0, where f' k x z = k $! f x z" , "f = " ++ showF , "z0 = " ++ showZ , "t = " ++ showT ] , lawContextReduced = reduced lhs rhs } c2 -> c2 c1 -> c1 heqCtx lhs rhs ctx foldableFoldl' :: ( Foldable f , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x) ) => (forall x. Gen x -> Gen (f x)) -> Property foldableFoldl' fgen = property $ do xs <- forAll $ fgen (genBottom genSmallInteger) let f :: Integer -> Bottom Integer -> Integer f a b = case b of BottomUndefined -> error "foldableFoldl': your foldl' is not strict!" BottomValue v -> if even v then a else v let z0 = 0 (rhs,ctx1) <- liftIO $ do let f' x k z = k $! f z x e <- try (evaluate (Foldable.foldr f' id xs z0)) case e of Left (_ :: ErrorCall) -> pure (Nothing, ctxNotStrict "foldl'") Right i -> pure (Just i, NoContext) (lhs,ctx2) <- liftIO $ do e <- try (evaluate (Foldable.foldl' f z0 xs)) case e of Left (_ :: ErrorCall) -> pure (Nothing, ctxNotStrict "foldl'") Right i -> pure (Just i, NoContext) let ctx = case ctx1 of NoContext -> case ctx2 of NoContext -> contextualise $ LawContext { lawContextLawName = "Foldl'" , lawContextLawBody = "foldl' f z0 xs" `congruency` "foldr f' id xs z0, where f' x k z = k $! f z x" , lawContextTcName = "Foldable" , lawContextTcProp = let showT = show xs showF = "\\a b -> case a of\n BottomUndefined -> error \"foldableFoldr': not strict\"\n BottomValue v -> if even v then v else b" showZ = show z0 in lawWhere [ "foldl' f z0 xs" `congruency` "foldr f' id xs z0, where f' x k z = k $! f z x" , "f = " ++ showF , "z0 = " ++ showZ , "t = " ++ showT ] , lawContextReduced = reduced lhs rhs } c2 -> c2 c1 -> c1 heqCtx lhs rhs ctx foldableFoldl1 :: ( Foldable f , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x) ) => (forall x. Gen x -> Gen (f x)) -> Property foldableFoldl1 fgen = property $ do e <- forAll genLinearEquationTwo t <- forAll $ fgen genSmallInteger case compatToList t of [] -> success (x:xs) -> let f = runLinearEquationTwo e lhs = Foldable.foldl1 f t rhs = Foldable.foldl f x xs ctx = contextualise $ LawContext { lawContextLawName = "Foldl1" , lawContextLawBody = "foldl1 f t" `congruency` "let (x:xs) = toList t in foldl f x xs" , lawContextTcName = "Foldable" , lawContextTcProp = let showF = show e showT = show t showX = show x showXS = show xs in lawWhere [ "foldl1 f t" `congruency` "let (x:xs) = toList t in foldl f x xs, where" , "f = " ++ showF , "t = " ++ showT , "x = " ++ showX , "xs = " ++ showXS ] , lawContextReduced = reduced lhs rhs } in heqCtx lhs rhs ctx foldableFoldr1 :: ( Foldable f , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x) ) => (forall x. Gen x -> Gen (f x)) -> Property foldableFoldr1 fgen = property $ do e <- forAll genLinearEquationTwo t <- forAll $ fgen genSmallInteger case unsnoc (compatToList t) of Nothing -> success Just (xs, x) -> let f = runLinearEquationTwo e lhs = Foldable.foldr1 f t rhs = Foldable.foldr f x xs ctx = contextualise $ LawContext { lawContextLawName = "Foldr1" , lawContextLawBody = "foldr1 f t" `congruency` "let (xs, x) = unsnoc (toList t) in foldr f x xs" , lawContextTcName = "Foldable" , lawContextTcProp = let showF = show e showT = show t showX = show x showXS = show xs in lawWhere [ "foldr1 f t" `congruency` "let (xs, x) = unsnoc (toList t) in foldr f x xs, where" , "f = " ++ showF , "t = " ++ showT , "x = " ++ showX , "xs = " ++ showXS ] , lawContextReduced = reduced lhs rhs } in heqCtx lhs rhs ctx foldableToList :: ( Foldable f , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x) ) => (forall x. Gen x -> Gen (f x)) -> Property foldableToList fgen = property $ do t <- forAll $ fgen genSmallInteger let lhs = Foldable.toList t let rhs = Foldable.foldr (:) [] t let ctx = contextualise $ LawContext { lawContextLawName = "ToList" , lawContextLawBody = "toList" `congruency` "foldr (:) []" , lawContextTcName = "Foldable" , lawContextTcProp = let showT = show t in lawWhere [ "toList t" `congruency` "foldr (:) [] t, where" , "t = " ++ showT ] , lawContextReduced = reduced lhs rhs } heqCtx lhs rhs ctx foldableNull :: ( Foldable f , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x) ) => (forall x. Gen x -> Gen (f x)) -> Property foldableNull fgen = property $ do t <- forAll $ fgen genSmallInteger let lhs = Foldable.null t let rhs = Foldable.foldr (const (const False)) True t let ctx = contextualise $ LawContext { lawContextLawName = "Null" , lawContextLawBody = "null" `congruency` "foldr (const (const False)) True" , lawContextTcName = "Foldable" , lawContextTcProp = let showT = show t in lawWhere [ "null t" `congruency` "foldr (const (const False)) True t, where" , "t = " ++ showT ] , lawContextReduced = reduced lhs rhs } heqCtx lhs rhs ctx foldableLength :: ( Foldable f , forall x. Eq x => Eq (f x), forall x. Show x => Show (f x) ) => (forall x. Gen x -> Gen (f x)) -> Property foldableLength fgen = property $ do t <- forAll $ fgen genSmallInteger let lhs = Foldable.length t let rhs = getSum (Foldable.foldMap (const (Sum 1)) t) let ctx = contextualise $ LawContext { lawContextLawName = "Length" , lawContextLawBody = "length" `congruency` "getSum . foldMap (const (Sum 1))" , lawContextTcName = "Foldable" , lawContextTcProp = let showT = show t in lawWhere [ "length t" `congruency` "getSum . foldMap (const (Sum 1)) $ t, where" , "t = " ++ showT ] , lawContextReduced = reduced lhs rhs } heqCtx lhs rhs ctx unsnoc :: [a] -> Maybe ([a], a) unsnoc = \case [] -> Nothing [x] -> Just ([], x) (x:y:xs) -> fmap (\(bs,b) -> (x:bs,b)) (unsnoc (y : xs)) compatToList :: Foldable f => f a -> [a] compatToList = Foldable.foldMap (\x -> [x])