free-algebras-0.0.8.0: test/Test/Control/Algebra/Free.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
module Test.Control.Algebra.Free
( tests
) where
import Control.Applicative.Free (Ap)
import qualified Control.Applicative.Free as Ap
import Control.Monad.Free (Free)
import qualified Control.Monad.Free as Free
import qualified Control.Monad.State.Strict as S
import Data.Functor (($>))
import Data.Functor.Identity (Identity (..))
import Data.Functor.Coyoneda (Coyoneda (..), lowerCoyoneda)
import Hedgehog (Property, PropertyT, Gen, property, (===))
import qualified Hedgehog as H
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import Text.Show.Functions ()
import Data.Algebra.Free ( AlgebraType )
import Data.Proxy (Proxy (..))
import Control.Algebra.Free
( AlgebraType0
, FreeAlgebra1 (..)
, Proof (..)
, unFoldNatFree
, foldFree1
, hoistFree1
, iterFree1
)
genIntToInt :: Integral n => Gen (n -> n)
genIntToInt = do
x <- Gen.integral $ Range.linear (-100) 100
return (+x)
showIntToInt :: (Integral n, Show n) => (n -> n) -> String
showIntToInt f = "(+"++ show (f 0) ++ ")"
-- |
-- Generate a @Coyoneda f@ given a constructor of @f@.
genCoyoneda
:: (Int -> f Int)
-> Gen (Coyoneda f Int)
genCoyoneda f = do
a <- Gen.int $ Range.linear 0 100
Gen.bool_ >>= \case
True -> return $ Coyoneda id (f a)
False -> do
x <- Gen.int $ Range.linear 0 100
return $ Coyoneda (\y -> y + a) (f x)
toOdd :: Integral n => n -> Maybe n
toOdd x = if x `mod` 2 == 0
then Nothing
else Just x
-- |
-- Generated `Ap Maybe` with arbitrary depth.
genAp :: forall x . Show x
=> Gen x
-> Gen (x -> x)
-> Gen (Ap Maybe x)
genAp gen genf = Gen.sized $ \s -> go s
where
go (Range.Size 0) = Gen.maybe gen >>= \case
Just x -> return $ Ap.Pure x
Nothing -> return $ Ap.Ap Nothing (Ap.Pure id)
go s = do
ap <- go (s - 1)
f <- genf
return $ Ap.Pure f <*> ap
genApIdentity
:: forall x . Show x
=> Gen x
-> Gen (x -> x)
-> Gen (Ap Identity x)
genApIdentity gen genf = Gen.sized $ \s -> go s
where
go (Range.Size 0) = do
x <- gen
return $ Ap.Ap (Identity x) (Ap.Pure id)
go s = do
ap <- go (s - 1)
f <- genf
return $ Ap.Pure f <*> ap
-- |
-- Generate @Free Maybe@ of arbitrary depth.
genFree :: Gen x
-> Gen (Free Maybe x)
genFree gen = Gen.sized go
where
go (Range.Size 0) = Free.Pure <$> gen
go s = Free.Free <$> Gen.maybe (go (s - 1))
genFreeIdentity
:: Gen x
-> Gen (Free Identity x)
genFreeIdentity gen = Gen.sized go
where
go (Range.Size 0) = Free.Pure <$> gen
go s = Free.Free . Identity <$> go (s - 1)
foldMapFree1_property
:: forall m f d a
. ( FreeAlgebra1 m
, AlgebraType m d
, AlgebraType m f
, AlgebraType0 m f
, Show a
, Show (f a)
, Eq (f a)
, Eq (d a)
, Show (d a)
)
=> Gen (m f a)
-> Gen (f a)
-> (forall x. f x -> d x)
-> (forall x. m f x -> d x)
-> Property
foldMapFree1_property gen_mfa gen_fa fd mfd
= property $ do
mfa <- H.forAllWith (show . foldFree1) gen_mfa
fa <- H.forAll gen_fa
H.assert $
unFoldNatFree (foldNatFree fd :: forall x. m f x -> d x) fa
==
fd fa
H.assert $
foldNatFree (unFoldNatFree mfd :: forall y. f y -> d y) mfa
==
mfd mfa
-- this is a consequence of the first property since
-- `foldFree1 = foldNatFree id`
H.assert $
unFoldNatFree (foldFree1 @m) fa
==
fa
prop_foldMapFree1_coyoneda :: Property
prop_foldMapFree1_coyoneda
= foldMapFree1_property
(genCoyoneda toOdd)
(Gen.maybe $ Gen.integral @_ @Int (Range.linear 0 1000))
id
foldFree1
prop_foldMapFree1_ap :: Property
prop_foldMapFree1_ap
= foldMapFree1_property
(genAp (Gen.word8 (Range.linear 0 254)) genIntToInt)
(Gen.maybe $ Gen.word8 (Range.linear 0 254))
id
foldFree1
prop_foldMapFree1_free :: Property
prop_foldMapFree1_free
= foldMapFree1_property
(genFree $ Gen.word8 (Range.linear 0 254))
(Gen.maybe $ Gen.word8 (Range.linear 0 254))
id
foldFree1
foldFree1_property
:: forall m f a
. ( FreeAlgebra1 m
, AlgebraType m f
, AlgebraType0 m f
, Eq (f a)
, Show (f a)
)
=> PropertyT IO (m f a)
-> (m f a -> f a)
-- ^ reference fold implentation
-> Property
foldFree1_property gen fold_ = property $ do
mfa <- gen
foldFree1 mfa === fold_ mfa
prop_foldFree1_coyoneda :: Property
prop_foldFree1_coyoneda =
foldFree1_property (H.forAll $ genCoyoneda toOdd) lowerCoyoneda
prop_foldFree1_ap :: Property
prop_foldFree1_ap = foldFree1_property
(H.forAllWith (show . Ap.retractAp) $ genAp (Gen.integral @_ @Int $ Range.linear 0 100) genIntToInt)
Ap.retractAp
prop_foldFree1_free :: Property
prop_foldFree1_free = foldFree1_property
(H.forAll $ genFree (Gen.integral @_ @Int $ Range.linear 0 100))
(Free.foldFree id)
hoistFree1_property
:: forall m f g a
. ( FreeAlgebra1 m
, AlgebraType m f
, AlgebraType m (m g)
, AlgebraType0 m f
, AlgebraType m g
, AlgebraType0 m g
)
=> Gen (m f a)
-> (m f a -> String)
-> (m g a -> m g a -> Bool)
-> (forall x. f x -> g x)
-> ((forall x. f x -> g x) -> m f a -> m g a)
-- ^ reference hoist impelentation
-> Property
hoistFree1_property gen show_mfa eq_mga nat refImpl = property $ do
mfa <- H.forAllWith show_mfa gen
H.assert $ hoistFree1 nat mfa `eq_mga` refImpl nat mfa
prop_hoistFree1_coyoneda :: Property
prop_hoistFree1_coyoneda = hoistFree1_property
(genCoyoneda toOdd)
(show . lowerCoyoneda)
(\a b -> lowerCoyoneda a == lowerCoyoneda b)
(maybe (Left ()) Right)
(\nat (Coyoneda xa fx) -> Coyoneda xa (nat fx))
prop_hoistFree1_ap :: Property
prop_hoistFree1_ap = hoistFree1_property
(genAp (Gen.int $ Range.linear 0 1000) genIntToInt)
(show . Ap.retractAp)
(\x y -> Ap.retractAp x == Ap.retractAp y)
(maybe (Left ()) Right)
Ap.hoistAp
prop_hoistFree1_free :: Property
prop_hoistFree1_free = hoistFree1_property
(genFree (Gen.integral @_ @Int $ Range.linear 0 100))
show
(==)
(maybe (Left ()) Right)
Free.hoistFree
iterFree1_property
:: forall m f a
. ( FreeAlgebra1 m
, AlgebraType m f
, AlgebraType0 m f
, AlgebraType m Identity
, AlgebraType0 m Identity
, Eq a
, Show a
)
=> Gen (m f a)
-> (m f a -> String)
-> (forall x. f x -> x)
-> ((forall x. f x -> x) -> m f a -> a)
-- ^ reference implementation
-> Property
iterFree1_property gen show_mfa nat refImpl = property $ do
mfa <- H.forAllWith show_mfa gen
iterFree1 nat mfa === refImpl nat mfa
prop_iterFree1_coyoneda :: Property
prop_iterFree1_coyoneda = iterFree1_property
(genCoyoneda Identity)
show
runIdentity
(\_ -> runIdentity . lowerCoyoneda)
prop_iterFree1_free :: Property
prop_iterFree1_free = iterFree1_property
(genFreeIdentity (Gen.int $ Range.linear 0 1000))
show
runIdentity
Free.iter
prop_iterFree1_ap :: Property
prop_iterFree1_ap = iterFree1_property
(genApIdentity (Gen.int $ Range.linear 0 1000) genIntToInt)
(show . Ap.retractAp)
runIdentity
Ap.iterAp
foldNatFree_liftFree_property
:: forall m g f a proxy n.
( FreeAlgebra1 m
, AlgebraType0 m f
, AlgebraType m g
, AlgebraType0 m g
, Eq (g a)
, Monad n
)
=> proxy m
-> Gen (f a)
-> (f a -> String)
-> (forall x. f x -> g x)
-> PropertyT n ()
foldNatFree_liftFree_property _ gen show_fa nat = do
(fa :: f a) <- H.forAllWith show_fa gen
H.assert $ (foldNatFree nat :: m f a -> g a) (liftFree fa :: m f a) == nat fa
prop_foldNatFree_liftFree_coyoneda :: Property
prop_foldNatFree_liftFree_coyoneda
= property $ foldNatFree_liftFree_property
(Proxy :: Proxy Coyoneda)
(Gen.maybe $ Gen.word8 (Range.linear 0 255))
show
(maybe (Left ()) Right)
prop_foldNatFree_liftFree_ap :: Property
prop_foldNatFree_liftFree_ap
= property $ foldNatFree_liftFree_property
(Proxy :: Proxy Ap)
(Gen.maybe $ Gen.word8 (Range.linear 0 255))
show
(maybe (Left ()) Right)
prop_foldNatFree_liftFree_free :: Property
prop_foldNatFree_liftFree_free
= property $ foldNatFree_liftFree_property
(Proxy :: Proxy Free)
(Gen.maybe $ Gen.word8 (Range.linear 0 255))
show
(maybe (Left ()) Right)
foldNatFree_foldNatFree_property
:: forall m f g h a proxy n.
( FreeAlgebra1 m
, AlgebraType0 m f
, AlgebraType0 m g
, AlgebraType m g
, AlgebraType m h
, Monad n
)
=> proxy m
-> Gen (m f a)
-> (m f a -> String)
-> (h a -> h a -> Bool)
-> (forall x. g x -> h x)
-> (forall x. f x -> m g x)
-> PropertyT n ()
foldNatFree_foldNatFree_property _ gen show_mfa eq_ha nat nat' =
case codom1 :: Proof (AlgebraType m (m g)) (m g) of
Proof -> do
(mfa :: m f a) <- H.forAllWith show_mfa gen
H.assert $ (foldNatFree nat :: m g a -> h a) (foldNatFree nat' mfa)
`eq_ha` foldNatFree (foldNatFree nat . nat') mfa
prop_foldNatFree_foldNatFree_coyoneda :: Property
prop_foldNatFree_foldNatFree_coyoneda = property $ do
n <- H.forAll $ Gen.integral (Range.linear 0 255)
foldNatFree_foldNatFree_property
(Proxy :: Proxy Coyoneda)
(genCoyoneda toOdd :: Gen (Coyoneda Maybe Int))
show
(==)
(maybe (Left ()) Right)
(liftFreeCoyonedaN n)
where
liftFreeCoyonedaN :: Int -> Maybe x -> Coyoneda Maybe x
liftFreeCoyonedaN 0 fx = liftFree fx
liftFreeCoyonedaN n fx = id <$> liftFreeCoyonedaN (pred n) fx
prop_foldNatFree_foldNatFree_ap :: Property
prop_foldNatFree_foldNatFree_ap
= property $ do
n <- H.forAll $ Gen.integral (Range.linear 0 255)
foldNatFree_foldNatFree_property
(Proxy :: Proxy Ap)
(genAp (Gen.integral (Range.linear 0 255)) genIntToInt :: Gen (Ap Maybe Int))
(const "blind")
(==)
(maybe (Left ()) Right)
(liftApN n id)
where
liftApN :: Int -> (x -> x) -> Maybe x -> Ap Maybe x
liftApN 0 _ ma = Ap.liftAp ma
liftApN n f ma = pure f <*> liftApN (pred n) f ma
prop_foldNatFree_foldNatFree_free :: Property
prop_foldNatFree_foldNatFree_free = property $ do
n <- H.forAll $ Gen.integral (Range.linear 0 255)
foldNatFree_foldNatFree_property
(Proxy :: Proxy Free)
(genFree (Gen.integral (Range.linear 0 255)) :: Gen (Free Maybe Int))
show
(==)
(maybe (Left ()) Right)
(liftFreeN n)
where
liftFreeN :: Int -> Maybe a -> Free Maybe a
liftFreeN 0 ma = liftFree ma
-- each bind wraps the lhs in a 'Free' constructor
liftFreeN n ma = liftFreeN (pred n) ma >>= liftFree . Just
prop_foldNatFree_foldNatFree_StateT :: Property
prop_foldNatFree_foldNatFree_StateT = property $ do
f1 <- H.forAll genIntToInt
f2 <- H.forAll genIntToInt
f3 <- H.forAll genIntToInt
foldNatFree_foldNatFree_property
(Proxy :: Proxy (S.StateT Int))
gen
(const "blind")
(\s s' -> S.runStateT s 0 == S.runStateT s' 0)
((\s -> s >>= \x -> S.modify f1 >> pure x)
:: forall x. S.StateT Int Identity x -> S.StateT Int Identity x)
((\(Identity x) -> S.lift (S.modify f2) >> S.modify f3 $> x)
:: forall x. Identity x -> S.StateT Int (S.StateT Int Identity) x)
where
gen :: Gen (S.StateT Int Identity Int)
gen = do
a <- Gen.int $ Range.linear 0 9
if a >= 3
then do
s <- Gen.int $ Range.linear 0 100
x <- Gen.int $ Range.linear 0 100
pure (S.put s $> x)
else pure S.get
tests :: IO Bool
tests = H.checkParallel $$(H.discover)