packages feed

free-algebras-0.1.0.1: 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
    (\f -> Free.iter f)

prop_iterFree1_ap :: Property
prop_iterFree1_ap = iterFree1_property
    (genApIdentity (Gen.int $ Range.linear 0 1000) genIntToInt)
    (show . Ap.retractAp)
    runIdentity
    (\f -> Ap.iterAp f)

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)