module Test.Trial.Laws ( lawsSpec -- * Internals , checkAssotiativityFor ) where import Control.Applicative (Alternative (..), liftA2) import Data.List.NonEmpty (NonEmpty (..)) import Data.Semigroup (Semigroup (..)) import Hedgehog (Gen, forAll, forAllWith, (===)) import Test.Hspec (Spec, describe, it, parallel) import Test.Hspec.Hedgehog (hedgehog) import Test.Trial.Gen (Property, genFunction, genFunction2, genInt, genSmallInt, genSmallList, genTrial) import Trial (Trial) lawsSpec :: Spec lawsSpec = describe "Trial Instance Laws" $ parallel $ do describe "Semigroup instance for Trial" $ do it "Associativity: a <> (b <> c) ≡ (a <> b) <> c" semigroupAssociativity it "Concatenation: sconcat ≡ foldr1 (<>)" semigroupConcatenation it "Times: stimes n a ≡ foldr1 (<>) (replicate n a)" semigroupTimes describe "Functor instance for Trial" $ do it "Identity: fmap id ≡ id" functorIdentity it "Composition: map f . fmap g ≡ fmap (f . g)" functorComposition it "Const: fmap (const x) ≡ x <$" functorConst describe "Applicative instance for Trial" $ do it "Functor and Applicative correspondence: fmap f x ≡ pure f <*> x" functorApplicative it "Identity: pure id <*> x ≡ x" applicativeIdentity it "Composition: pure (.) <*> f <*> g <*> x ≡ f <*> (g <*> x)" applicativeComposition it "Homomorphism: pure f <*> pure x ≡ pure (f x)" applicativeHomomorphism it "Interchange: f <*> pure x ≡ pure ($ x) <*> f" applicativeInterchange it "Apply Right: u *> v ≡ (id <$ u) <*> v" applicativeApplyRight it "Apply Left: u <* v ≡ liftA2 const u v" applicativeApplyLeft it "(<*>) via liftA2: (<*>) ≡ liftA2 id" applicativeApViaLiftA2 it "liftA2 via (<*>): liftA2 f x y ≡ f <$> x <*> y" applicativeLiftA2ViaAp describe "Alternative instance for Trial" $ do it "Associativity: a <|> (b <|> c) ≡ (a <|> b) <|> c" alternativeAssociativity it "Right Identity: x <|> empty ≡ x" alternativeRightIdentity it "Left Identity: empty <|> x ≡ x" alternativeLeftIdentity ---------------------------------------------------------------------------- -- Semigroup instance properties ---------------------------------------------------------------------------- semigroupAssociativity :: Property semigroupAssociativity = checkAssotiativityFor (genTrial genInt) (<>) semigroupConcatenation :: Property semigroupConcatenation = do let gen = genTrial genInt a <- forAll gen as <- forAll $ genSmallList gen let ne = a :| as sconcat ne === foldr1 (<>) ne semigroupTimes :: Property semigroupTimes = do a <- forAll $ genTrial genInt n <- forAll genSmallInt stimes n a === foldr1 (<>) (replicate n a) ---------------------------------------------------------------------------- -- Functor instance laws ---------------------------------------------------------------------------- functorIdentity :: Property functorIdentity = hedgehog $ do a <- forAll $ genTrial genInt fmap id a === id a functorComposition :: Property functorComposition = hedgehog $ do a <- forAll $ genTrial genInt f <- forAllWith (const "f") genFunction g <- forAllWith (const "g") genFunction fmap f (fmap g a) === fmap (f . g) a functorConst :: Property functorConst = hedgehog $ do a <- forAll $ genTrial genInt let x = 'X' fmap (const x) a === (x <$ a) ---------------------------------------------------------------------------- -- Applicative instance properties ---------------------------------------------------------------------------- functorApplicative :: Property functorApplicative = hedgehog $ do f <- forAllWith (const "f") genFunction x <- forAll $ genTrial genInt fmap f x === (pure f <*> x) applicativeIdentity :: Property applicativeIdentity = hedgehog $ do vx <- forAll $ genTrial genInt (pure id <*> vx) === vx applicativeComposition :: Property applicativeComposition = hedgehog $ do vf <- forAllWith (const "f") $ genTrial genFunction vg <- forAllWith (const "g") $ genTrial genFunction vx <- forAll $ genTrial genInt (pure (.) <*> vf <*> vg <*> vx) === (vf <*> (vg <*> vx)) applicativeHomomorphism :: Property applicativeHomomorphism = hedgehog $ do f <- forAllWith (const "f") genFunction x <- forAll genInt (pure f <*> pure x) === pure @(Trial Int) (f x) applicativeInterchange :: Property applicativeInterchange = hedgehog $ do vf <- forAllWith (const "f") $ genTrial genFunction x <- forAll genInt (vf <*> pure x) === (pure ($ x) <*> vf) applicativeApplyRight :: Property applicativeApplyRight = hedgehog $ do let genVal = genTrial genInt vy <- forAll genVal vx <- forAll genVal (vy *> vx) === ((id <$ vy) <*> vx) applicativeApplyLeft :: Property applicativeApplyLeft = hedgehog $ do let genVal = genTrial genInt vy <- forAll genVal vx <- forAll genVal (vy <* vx) === liftA2 const vy vx applicativeApViaLiftA2 :: Property applicativeApViaLiftA2 = hedgehog $ do vf <- forAllWith (const "f") $ genTrial genFunction vx <- forAll $ genTrial genInt (vf <*> vx) === liftA2 id vf vx applicativeLiftA2ViaAp :: Property applicativeLiftA2ViaAp = hedgehog $ do f <- forAllWith (const "f") genFunction2 vx <- forAll $ genTrial genInt vy <- forAll $ genTrial genInt liftA2 f vx vy === (f <$> vx <*> vy) ---------------------------------------------------------------------------- -- Alternative instance properties ---------------------------------------------------------------------------- alternativeAssociativity :: Property alternativeAssociativity = checkAssotiativityFor (genTrial genInt) (<|>) alternativeRightIdentity :: Property alternativeRightIdentity = hedgehog $ do x <- forAll $ genTrial genSmallInt (x <|> empty) === x alternativeLeftIdentity :: Property alternativeLeftIdentity = hedgehog $ do x <- forAll $ genTrial genInt (empty <|> x) === x ---------------------------------------------------------------------------- -- Property helpers ---------------------------------------------------------------------------- {- | Property test for the associativity law: @ a ⊗ (b ⊗ c) ≡ (a ⊗ b) ⊗ c @ -} checkAssotiativityFor :: (Show a, Eq a) => Gen a -> (a -> a -> a) -> Property checkAssotiativityFor gen op = hedgehog $ do a <- forAll gen b <- forAll gen c <- forAll gen a `op` (b `op` c) === (a `op` b) `op` c