packages feed

free-category-0.0.4.4: test/Test/Cat.hs

{-# LANGUAGE CPP                   #-}
{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE FlexibleInstances     #-}
#if __GLASGOW_HASKELL__ >= 902
{-# LANGUAGE FlexibleContexts      #-}
#endif
{-# LANGUAGE GADTs                 #-}
{-# LANGUAGE KindSignatures        #-}
{-# LANGUAGE PolyKinds             #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TypeApplications      #-}
{-# LANGUAGE TupleSections         #-}
{-# LANGUAGE UndecidableInstances  #-}
{-# LANGUAGE QuantifiedConstraints #-}

{-# OPTIONS_GHC -Wno-orphans #-}

module Test.Cat (tests) where

import           Prelude hiding ((.), id)
import           Control.Category
import           Data.Function (on)
import           Text.Show.Functions ()
import           Numeric.Natural (Natural)

import           Control.Algebra.Free2
import           Control.Category.Free

import           Test.QuickCheck
import           Test.Tasty (TestTree, testGroup)
import           Test.Tasty.QuickCheck (testProperty)

tests :: TestTree
tests =
  testGroup "Control.Category.Free"
  [ testProperty "Queue" prop_Queue
  , testProperty "C"     prop_C
  , testGroup "Category laws"
    [ testProperty "ListTr id"            prop_id_ListTr
    , testProperty "ListTr associativity" prop_associativity_ListTr
    , testProperty "Queue id"            prop_id_Queue
    , testProperty "Queue associativity" prop_associativity_Queue
    , testProperty "C id"                 prop_id_C
    , testProperty "C associativity"      prop_associativity_C
    ]
  , testGroup "foldFree2 and foldMap"
    [ testProperty "foldFree ListTr" prop_foldListTr
    , testProperty "foldFree Queue"  prop_foldQueue
    , testProperty "foldFree C"      prop_foldC
    ]
  ]


data Tr a b where
    -- Num transition
    NumTr       :: Num a => (a -> a) -> Tr a a
    FromInteger :: Num b => Tr Integer b

    -- Integral transition
    ToInteger  :: Integral a => Tr a Integer


interpretTr :: Tr a b -> a -> b
interpretTr (NumTr f)   = f
interpretTr FromInteger = fromInteger
interpretTr ToInteger   = toInteger


instance (Show a, Show b) => Show (Tr a b) where
    show (NumTr f)   = "NumTr " ++ show f
    show FromInteger = "FromInteger"
    show ToInteger   = "ToInteger"


data SomeNumTr f a where
    SomeNumTr :: Num a
              => f Tr a a
              -> SomeNumTr f a

instance Show (f Tr a a) => Show (SomeNumTr f a) where
      show (SomeNumTr f) = "SomeNumTr " ++ show f


data SomeIntegralTr f a where
    SomeIntegralTr :: Integral a
                   => f Tr a a
                   -> SomeIntegralTr f a

instance Show (f Tr a a) => Show (SomeIntegralTr f a) where
      show (SomeIntegralTr f) = "SomeIntegralTr " ++ show f


-- A 'fromIntegral' transition in any free category @f@.
fromIntegralTr :: ( Integral a
                  , Num b
                  , Category     (f Tr)
                  , AlgebraType0 f Tr
                  , FreeAlgebra2 f
                  ) => f Tr a b
fromIntegralTr = liftFree2 FromInteger . liftFree2 ToInteger


data Sing a where
    SInt     :: Sing Int
    SInteger :: Sing Integer
    SNatural :: Sing Natural

instance Show (Sing a) where
    show SInt     = "SInt"
    show SInteger = "SInteger"
    show SNatural = "SNatural"

data AnySing where
    AnySing :: Eq a => Sing a -> AnySing

instance Eq AnySing where
    AnySing SInt     == AnySing SInt     = True
    AnySing SInteger == AnySing SInteger = True
    AnySing SNatural == AnySing SNatural = True
    _                == _                = False

instance Show AnySing where
    show (AnySing sing) = show sing

instance Arbitrary AnySing where
    arbitrary = oneof
      [ pure $ AnySing SInt
      , pure $ AnySing SInteger
      , pure $ AnySing SNatural
      ]


instance Arbitrary Natural where
    arbitrary =
      fromIntegral . getPositive <$> (arbitrary :: Gen (Positive Integer))

instance CoArbitrary Natural where
    coarbitrary a = variant (fromIntegral a :: Int)

data AnyListTr b where
    AnyListTr :: Eq c => ListTr Tr b c -> Sing c -> AnyListTr b


genNextTr :: Sing b
          -> Gen (AnyListTr b)
genNextTr b = do
    AnySing c <- arbitrary
    case (b, c) of
      (SInt, SInt) ->
        (\f -> AnyListTr (ConsTr (NumTr f) NilTr) c) <$> arbitrary
      (SInteger, SInteger) ->
        (\f -> AnyListTr (ConsTr (NumTr f) NilTr) c) <$> arbitrary
      (SNatural, SNatural) ->
        (\f -> AnyListTr (ConsTr (NumTr f) NilTr) c) <$> arbitrary

      (SInt, SInteger) ->
        pure $ AnyListTr fromIntegralTr c
      (SInt, SNatural) ->
        pure $ AnyListTr (fromIntegralTr . liftFree2 (NumTr abs)) c
      (SInteger, SInt) ->
        pure $ AnyListTr fromIntegralTr c
      (SNatural, SInt) ->
        pure $ AnyListTr fromIntegralTr c
      (SNatural, SInteger) ->
        pure $ AnyListTr fromIntegralTr c
      (SInteger, SNatural) ->
        pure $ AnyListTr (fromIntegralTr . liftFree2 (NumTr abs)) c


data ArbListTr where
    ArbListTr :: Eq b => ListTr Tr a b -> Sing a -> Sing b -> ArbListTr

instance (forall x y. Show (Tr x y)) => Show ArbListTr where
    show (ArbListTr listTr a b) =
         "ArbListTr "
      ++ show a
      ++ " -> "
      ++ show b
      ++ " "
      ++ show listTr

instance Arbitrary ArbListTr where
    arbitrary = sized $ \n -> do
        k <- choose (0, n)
        AnySing a <- arbitrary
        go k a (AnyListTr NilTr a)
      where
        go 0 a (AnyListTr ab b) = pure $ ArbListTr ab a b
        go n a (AnyListTr ab b) = do
          AnyListTr bc c <- genNextTr b
          -- (.) can be used as (++) for ListTr
          go (n - 1) a $ AnyListTr (bc . ab) c


--
-- test 'Cat' and 'C' treating 'ListTr' as a model to compare to.
--
prop_Queue, prop_C
    :: Blind ArbListTr -> Bool


prop_Queue (Blind (ArbListTr listTr SInt _)) =
      foldNatFree2 interpretTr (hoistFreeH2 @_ @Queue listTr) 0
    ==
      foldNatFree2 interpretTr listTr 0
prop_Queue (Blind (ArbListTr listTr SInteger _)) =
      foldNatFree2 interpretTr (hoistFreeH2 @_ @Queue listTr) 0
    ==
      foldNatFree2 interpretTr listTr 0
prop_Queue (Blind (ArbListTr listTr SNatural _)) =
      foldNatFree2 interpretTr (hoistFreeH2 @_ @Queue listTr) 0
    ==
      foldNatFree2 interpretTr listTr 0


prop_C (Blind (ArbListTr listTr SInt _)) =
      foldNatFree2 interpretTr (hoistFreeH2 @_ @C listTr) 0
    ==
      foldNatFree2 interpretTr listTr 0
prop_C (Blind (ArbListTr listTr SInteger _)) =
      foldNatFree2 interpretTr (hoistFreeH2 @_ @C listTr) 0
    ==
      foldNatFree2 interpretTr listTr 0
prop_C (Blind (ArbListTr listTr SNatural _)) =
      foldNatFree2 interpretTr (hoistFreeH2 @_ @C listTr) 0
    ==
      foldNatFree2 interpretTr listTr 0

--
-- Test Category Laws
-- @
--  f . id == f == id . f
--  f . g . h == (f . g) . h
-- @
--

prop_id :: Category c
        => (c a b -> c a b -> Bool)
        -> c a b
        -> Bool
prop_id eqCat f = eqCat (f . id) f && eqCat (id . f) f

prop_associativity :: Category c
                   => (c x w -> c x w -> Bool)
                   -> c z w -> c y z -> c x y
                   -> Bool
prop_associativity eqCat f g h =
    (f . g . h) `eqCat` ((f . g) . h)


-- | Integers form commutative monoid, and thus a category (a groupoid to be
-- precise) with a single object.
--
data IntCat (a :: ()) (b :: ()) where
     IntCat :: Int -> IntCat a a

instance Show (IntCat a b) where
    show (IntCat i) = "IntCat " ++ show i

instance Eq (IntCat a b) where
    IntCat i  == IntCat j = i == j

instance Category IntCat where
    id = IntCat 0
    IntCat a . IntCat b = IntCat (a + b)

instance Semigroup (IntCat '() '()) where
    IntCat a <> IntCat b = IntCat (a + b)

instance Monoid (IntCat '() '()) where
    mempty = IntCat 0

instance Arbitrary (IntCat '() '()) where
    arbitrary = IntCat <$> arbitrary

fromList :: forall k (a :: k) m f.
            ( FreeAlgebra2 m
            , AlgebraType0 m f
            , Category    (m f)
            ) => [f a a] -> m f a a
fromList [] = id
fromList (f : fs) = liftFree2 f . fromList fs

toList :: ( FreeAlgebra2 m
          , AlgebraType0 m IntCat
          , AlgebraType  m (ListTr IntCat)
          )
       => m IntCat '() '()
       -> [IntCat '() '()]
toList c = go (hoistFreeH2 c)
  where
    go :: ListTr IntCat '() '() -> [IntCat '() '()]
    go NilTr = []
    go (ConsTr tr@IntCat{} xs) = tr : go xs

--
-- 'C' category laws
--

newtype ArbIntC = ArbIntC (C IntCat '() '())

instance Show ArbIntC where
    show (ArbIntC c) = show c

instance Arbitrary ArbIntC where
    arbitrary = ArbIntC . fromList <$> arbitrary
    shrink (ArbIntC c) =
      map (ArbIntC . fromList)
          $ shrinkList (const [])
          $ toList c

prop_id_C :: ArbIntC -> Bool
prop_id_C (ArbIntC f) =
    prop_id (on (==) toList) f

prop_associativity_C
    :: ArbIntC -> ArbIntC -> ArbIntC
    -> Bool
prop_associativity_C (ArbIntC f0)
                     (ArbIntC f1)
                     (ArbIntC f2) =
      prop_associativity (on (==) toList) f0 f1 f2

--
-- 'Queue' category laws
--

newtype ArbIntQueue = ArbIntQueue (Queue IntCat '() '())

instance Show ArbIntQueue where
    show (ArbIntQueue f) = show (toList f)

instance Arbitrary ArbIntQueue where
    arbitrary = ArbIntQueue . fromList <$> arbitrary
    shrink (ArbIntQueue c) =
      map (ArbIntQueue . fromList)
          $ shrinkList (const [])
          $ toList c

prop_id_Queue :: ArbIntQueue -> Bool
prop_id_Queue (ArbIntQueue f) =
    prop_id (on (==) toList) f

prop_associativity_Queue
    :: ArbIntQueue -> ArbIntQueue -> ArbIntQueue
    -> Bool
prop_associativity_Queue (ArbIntQueue f0)
                         (ArbIntQueue f1)
                         (ArbIntQueue f2) =
      prop_associativity (on (==) toList) f0 f1 f2

--
-- 'ListTr' category laws
--

newtype ArbIntListTr = ArbIntListTr (ListTr IntCat '() '())

instance Show ArbIntListTr where
    show (ArbIntListTr f) = show (toList f)

instance Arbitrary ArbIntListTr where
    arbitrary = ArbIntListTr . fromList <$> arbitrary
    shrink (ArbIntListTr c) =
      map (ArbIntListTr . fromList)
          $ shrinkList (const [])
          $ toList c

prop_id_ListTr :: ArbIntListTr -> Bool
prop_id_ListTr (ArbIntListTr f) =
    prop_id (on (==) toList) f

prop_associativity_ListTr
    :: ArbIntListTr -> ArbIntListTr -> ArbIntListTr
    -> Bool
prop_associativity_ListTr (ArbIntListTr f0)
                          (ArbIntListTr f1)
                          (ArbIntListTr f2) =
      prop_associativity (on (==) toList) f0 f1 f2


--
-- Compatibility between 'foldFree2' and 'foldMap' for 'IntCat'
--

prop_foldListTr :: ArbIntListTr -> Bool
prop_foldListTr (ArbIntListTr f)
    = foldFree2 f == foldMap id (toList f)

prop_foldQueue :: ArbIntQueue -> Bool
prop_foldQueue (ArbIntQueue f)
    = foldFree2 f == foldMap id (toList f)

prop_foldC :: (Blind ArbIntC) -> Bool
prop_foldC (Blind (ArbIntC f))
    = foldFree2 f == foldMap id (toList f)