{-# 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
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])