alignment-0.2.0.1: test/Laws.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wall #-}
module Main (main) where
import Data.Alignment
import Data.Bifunctor (bimap)
import Data.Functor.Identity (Identity (..))
import qualified Data.IntMap as IntMap
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.Map as Map
import qualified Data.Sequence as Seq
import qualified Data.Vector as Vector
import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import System.Exit (exitFailure, exitSuccess)
import System.IO (hSetEncoding, stderr, stdout, utf8)
-- * Generators
genList :: Gen a -> Gen [a]
genList = Gen.list (Range.linear 0 20)
genNonEmpty :: Gen a -> Gen (NonEmpty a)
genNonEmpty g = (:|) <$> g <*> genList g
genMaybe :: Gen a -> Gen (Maybe a)
genMaybe = Gen.maybe
genVector :: Gen a -> Gen (Vector.Vector a)
genVector = fmap Vector.fromList . genList
genSeq :: Gen a -> Gen (Seq.Seq a)
genSeq = fmap Seq.fromList . genList
genMap :: (Ord k) => Gen k -> Gen v -> Gen (Map.Map k v)
genMap genK genV = Map.fromList <$> genList ((,) <$> genK <*> genV)
genIntMap :: Gen v -> Gen (IntMap.IntMap v)
genIntMap genV = IntMap.fromList <$> genList ((,) <$> Gen.int (Range.linear 0 100) <*> genV)
genInt :: Gen Int
genInt = Gen.int (Range.linear (-100) 100)
genChar :: Gen Char
genChar = Gen.alpha
-- * Test properties
-- ** Functor laws for This
prop_functorIdentity_List :: Property
prop_functorIdentity_List = property $ do
this <- forAll $ genThisListNonEmpty genInt genInt
this === this
prop_functorComposition_List :: Property
prop_functorComposition_List = property $ do
this <- forAll $ genThisListNonEmpty genInt genInt
let f = (+ 1)
let g = (* 2)
fmap (f . g) this === fmap f (fmap g this)
-- ** Bifunctor laws for This
prop_bifunctorIdentity_List :: Property
prop_bifunctorIdentity_List = property $ do
this <- forAll $ genThisListNonEmpty genInt genInt
this === this
prop_bifunctorComposition_List :: Property
prop_bifunctorComposition_List = property $ do
this <- forAll $ genThisListNonEmpty genInt genInt
let f1 = (+ 1)
let f2 = (* 2)
let g1 = (+ 10)
let g2 = (* 20)
bimap (f1 . f2) (g1 . g2) this === bimap f1 g1 (bimap f2 g2 this)
-- ** Semialign laws - List
prop_semialignNaturality_List :: Property
prop_semialignNaturality_List = property $ do
xs <- forAll $ genList genInt
ys <- forAll $ genList genInt
let f = (+ 1)
let g = (* 2)
semialignNaturality f g xs ys === True
prop_semialignSymmetry_List :: Property
prop_semialignSymmetry_List = property $ do
xs <- forAll $ genList genInt
ys <- forAll $ genList genInt
semialignSymmetry xs ys === True
prop_semialignCoherence_List :: Property
prop_semialignCoherence_List = property $ do
xs <- forAll $ genList genInt
ys <- forAll $ genList genInt
semialignCoherence xs ys === True
prop_semialignWithLaw_List :: Property
prop_semialignWithLaw_List = property $ do
xs <- forAll $ genList genInt
ys <- forAll $ genList genInt
let g = (* 2)
h = (* 3)
f (a, b) = (g a, h b)
semialignWithLaw f g h xs ys === True
-- ** Semialign laws - Maybe
prop_semialignNaturality_Maybe :: Property
prop_semialignNaturality_Maybe = property $ do
xs <- forAll $ genMaybe genInt
ys <- forAll $ genMaybe genInt
let f = (+ 1)
let g = (* 2)
semialignNaturality f g xs ys === True
prop_semialignSymmetry_Maybe :: Property
prop_semialignSymmetry_Maybe = property $ do
xs <- forAll $ genMaybe genInt
ys <- forAll $ genMaybe genInt
semialignSymmetry xs ys === True
prop_semialignCoherence_Maybe :: Property
prop_semialignCoherence_Maybe = property $ do
xs <- forAll $ genMaybe genInt
ys <- forAll $ genMaybe genInt
semialignCoherence xs ys === True
-- ** Semialign laws - NonEmpty
prop_semialignNaturality_NonEmpty :: Property
prop_semialignNaturality_NonEmpty = property $ do
xs <- forAll $ genNonEmpty genInt
ys <- forAll $ genNonEmpty genInt
let f = (+ 1)
let g = (* 2)
semialignNaturality f g xs ys === True
prop_semialignSymmetry_NonEmpty :: Property
prop_semialignSymmetry_NonEmpty = property $ do
xs <- forAll $ genNonEmpty genInt
ys <- forAll $ genNonEmpty genInt
semialignSymmetry xs ys === True
prop_semialignCoherence_NonEmpty :: Property
prop_semialignCoherence_NonEmpty = property $ do
xs <- forAll $ genNonEmpty genInt
ys <- forAll $ genNonEmpty genInt
semialignCoherence xs ys === True
-- ** Semialign laws - Vector
prop_semialignNaturality_Vector :: Property
prop_semialignNaturality_Vector = property $ do
xs <- forAll $ genVector genInt
ys <- forAll $ genVector genInt
let f = (+ 1)
let g = (* 2)
semialignNaturality f g xs ys === True
prop_semialignSymmetry_Vector :: Property
prop_semialignSymmetry_Vector = property $ do
xs <- forAll $ genVector genInt
ys <- forAll $ genVector genInt
semialignSymmetry xs ys === True
prop_semialignCoherence_Vector :: Property
prop_semialignCoherence_Vector = property $ do
xs <- forAll $ genVector genInt
ys <- forAll $ genVector genInt
semialignCoherence xs ys === True
-- ** Semialign laws - Seq
prop_semialignNaturality_Seq :: Property
prop_semialignNaturality_Seq = property $ do
xs <- forAll $ genSeq genInt
ys <- forAll $ genSeq genInt
let f = (+ 1)
let g = (* 2)
semialignNaturality f g xs ys === True
prop_semialignSymmetry_Seq :: Property
prop_semialignSymmetry_Seq = property $ do
xs <- forAll $ genSeq genInt
ys <- forAll $ genSeq genInt
semialignSymmetry xs ys === True
prop_semialignCoherence_Seq :: Property
prop_semialignCoherence_Seq = property $ do
xs <- forAll $ genSeq genInt
ys <- forAll $ genSeq genInt
semialignCoherence xs ys === True
-- ** Semialign laws - Map
-- Note: Map and IntMap do NOT satisfy the symmetry law when both sides
-- have disjoint keys (leftovers). When both have leftovers, left takes
-- precedence. This is a known limitation and why they don't have Unalign
-- instances. We still test naturality and coherence which do hold.
prop_semialignNaturality_Map :: Property
prop_semialignNaturality_Map = property $ do
xs <- forAll $ genMap genInt genChar
ys <- forAll $ genMap genInt genChar
let f = toEnum . (+ 1) . fromEnum :: Char -> Char
let g = toEnum . (+ 2) . fromEnum :: Char -> Char
semialignNaturality f g xs ys === True
-- Skip symmetry test for Map - known to fail when both sides have disjoint keys
-- prop_semialignSymmetry_Map :: Property
-- prop_semialignSymmetry_Map = property $ do
-- xs <- forAll $ genMap genInt genChar
-- ys <- forAll $ genMap genInt genChar
-- semialignSymmetry xs ys === True
prop_semialignCoherence_Map :: Property
prop_semialignCoherence_Map = property $ do
xs <- forAll $ genMap genInt genChar
ys <- forAll $ genMap genInt genChar
semialignCoherence xs ys === True
-- ** Semialign laws - IntMap
-- Note: IntMap, like Map, does NOT satisfy the symmetry law when both sides
-- have disjoint keys (leftovers). When both have leftovers, left takes
-- precedence. This is a known limitation and why they don't have Unalign
-- instances.
prop_semialignNaturality_IntMap :: Property
prop_semialignNaturality_IntMap = property $ do
xs <- forAll $ genIntMap genChar
ys <- forAll $ genIntMap genChar
let f = toEnum . (+ 1) . fromEnum :: Char -> Char
let g = toEnum . (+ 2) . fromEnum :: Char -> Char
semialignNaturality f g xs ys === True
-- Skip symmetry test for IntMap - known to fail when both sides have disjoint keys
-- prop_semialignSymmetry_IntMap :: Property
-- prop_semialignSymmetry_IntMap = property $ do
-- xs <- forAll $ genIntMap genChar
-- ys <- forAll $ genIntMap genChar
-- semialignSymmetry xs ys === True
prop_semialignCoherence_IntMap :: Property
prop_semialignCoherence_IntMap = property $ do
xs <- forAll $ genIntMap genChar
ys <- forAll $ genIntMap genChar
semialignCoherence xs ys === True
-- ** Align laws - List
prop_alignRightIdentity_List :: Property
prop_alignRightIdentity_List = property $ do
xs <- forAll $ genList genInt
alignRightIdentity xs ([] :: [Char]) === True
prop_alignLeftIdentity_List :: Property
prop_alignLeftIdentity_List = property $ do
ys <- forAll $ genList genInt
alignLeftIdentity ([] :: [Char]) ys === True
prop_alignEmpty_List :: Property
prop_alignEmpty_List = property $ do
alignEmpty (undefined :: [Int]) (undefined :: [Int]) === True
-- ** Align laws - Maybe
prop_alignRightIdentity_Maybe :: Property
prop_alignRightIdentity_Maybe = property $ do
xs <- forAll $ genMaybe genInt
alignRightIdentity xs (Nothing :: Maybe Char) === True
prop_alignLeftIdentity_Maybe :: Property
prop_alignLeftIdentity_Maybe = property $ do
ys <- forAll $ genMaybe genInt
alignLeftIdentity (Nothing :: Maybe Char) ys === True
prop_alignEmpty_Maybe :: Property
prop_alignEmpty_Maybe = property $ do
alignEmpty (undefined :: Maybe Int) (undefined :: Maybe Int) === True
-- ** Align laws - Vector
prop_alignRightIdentity_Vector :: Property
prop_alignRightIdentity_Vector = property $ do
xs <- forAll $ genVector genInt
alignRightIdentity xs (Vector.empty :: Vector.Vector Char) === True
prop_alignLeftIdentity_Vector :: Property
prop_alignLeftIdentity_Vector = property $ do
ys <- forAll $ genVector genInt
alignLeftIdentity (Vector.empty :: Vector.Vector Char) ys === True
prop_alignEmpty_Vector :: Property
prop_alignEmpty_Vector = property $ do
alignEmpty (undefined :: Vector.Vector Int) (undefined :: Vector.Vector Int) === True
-- ** Align laws - Seq
prop_alignRightIdentity_Seq :: Property
prop_alignRightIdentity_Seq = property $ do
xs <- forAll $ genSeq genInt
alignRightIdentity xs (Seq.empty :: Seq.Seq Char) === True
prop_alignLeftIdentity_Seq :: Property
prop_alignLeftIdentity_Seq = property $ do
ys <- forAll $ genSeq genInt
alignLeftIdentity (Seq.empty :: Seq.Seq Char) ys === True
prop_alignEmpty_Seq :: Property
prop_alignEmpty_Seq = property $ do
alignEmpty (undefined :: Seq.Seq Int) (undefined :: Seq.Seq Int) === True
-- ** Align laws - Map
prop_alignRightIdentity_Map :: Property
prop_alignRightIdentity_Map = property $ do
xs <- forAll $ genMap genInt genChar
alignRightIdentity xs (Map.empty :: Map.Map Int Char) === True
prop_alignLeftIdentity_Map :: Property
prop_alignLeftIdentity_Map = property $ do
ys <- forAll $ genMap genInt genChar
alignLeftIdentity (Map.empty :: Map.Map Int Char) ys === True
prop_alignEmpty_Map :: Property
prop_alignEmpty_Map = property $ do
alignEmpty (undefined :: Map.Map Int Char) (undefined :: Map.Map Int Char) === True
-- ** Align laws - IntMap
prop_alignRightIdentity_IntMap :: Property
prop_alignRightIdentity_IntMap = property $ do
xs <- forAll $ genIntMap genChar
alignRightIdentity xs (IntMap.empty :: IntMap.IntMap Char) === True
prop_alignLeftIdentity_IntMap :: Property
prop_alignLeftIdentity_IntMap = property $ do
ys <- forAll $ genIntMap genChar
alignLeftIdentity (IntMap.empty :: IntMap.IntMap Char) ys === True
prop_alignEmpty_IntMap :: Property
prop_alignEmpty_IntMap = property $ do
alignEmpty (undefined :: IntMap.IntMap Char) (undefined :: IntMap.IntMap Char) === True
-- ** Unalign laws - List
prop_unalignRoundtrip_List :: Property
prop_unalignRoundtrip_List = property $ do
xs <- forAll $ genList genInt
ys <- forAll $ genList genInt
unalignRoundtrip xs ys === True
prop_unalignNaturality_List :: Property
prop_unalignNaturality_List = property $ do
this <- forAll $ genThisListNonEmpty genInt genInt
let f = (+ 1)
let g = (* 2)
unalignNaturality f g this === True
-- ** Unalign laws - Maybe
prop_unalignRoundtrip_Maybe :: Property
prop_unalignRoundtrip_Maybe = property $ do
xs <- forAll $ genMaybe genInt
ys <- forAll $ genMaybe genInt
unalignRoundtrip xs ys === True
prop_unalignNaturality_Maybe :: Property
prop_unalignNaturality_Maybe = property $ do
this <- forAll $ genThisMaybeIdentity genInt genInt
let f = (+ 1)
let g = (* 2)
unalignNaturality f g this === True
-- ** Unalign laws - NonEmpty
prop_unalignRoundtrip_NonEmpty :: Property
prop_unalignRoundtrip_NonEmpty = property $ do
xs <- forAll $ genNonEmpty genInt
ys <- forAll $ genNonEmpty genInt
unalignRoundtrip xs ys === True
prop_unalignNaturality_NonEmpty :: Property
prop_unalignNaturality_NonEmpty = property $ do
this <- forAll $ genThisNonEmptyNonEmpty genInt genInt
let f = (+ 1)
let g = (* 2)
unalignNaturality f g this === True
-- ** Unalign laws - Vector
prop_unalignRoundtrip_Vector :: Property
prop_unalignRoundtrip_Vector = property $ do
xs <- forAll $ genVector genInt
ys <- forAll $ genVector genInt
unalignRoundtrip xs ys === True
prop_unalignNaturality_Vector :: Property
prop_unalignNaturality_Vector = property $ do
this <- forAll $ genThisVectorNonEmpty genInt genInt
let f = (+ 1)
let g = (* 2)
unalignNaturality f g this === True
-- ** Unalign laws - Seq
prop_unalignRoundtrip_Seq :: Property
prop_unalignRoundtrip_Seq = property $ do
xs <- forAll $ genSeq genInt
ys <- forAll $ genSeq genInt
unalignRoundtrip xs ys === True
prop_unalignNaturality_Seq :: Property
prop_unalignNaturality_Seq = property $ do
this <- forAll $ genThisSeqNonEmpty genInt genInt
let f = (+ 1)
let g = (* 2)
unalignNaturality f g this === True
-- * Helper generators
genThisListNonEmpty :: Gen a -> Gen b -> Gen (This [] NonEmpty a b)
genThisListNonEmpty genA genB = Gen.choice [viaAlign, withLeftover, withRightover, justPairs]
where
viaAlign = do
as <- genList genA
bs <- genList genB
return $ align as bs
withLeftover = do
pairs <- genList ((,) <$> genA <*> genB)
leftover <- genNonEmpty genA
return $ This pairs (Just (Left leftover))
withRightover = do
pairs <- genList ((,) <$> genA <*> genB)
rightover <- genNonEmpty genB
return $ This pairs (Just (Right rightover))
justPairs = do
pairs <- genList ((,) <$> genA <*> genB)
return $ This pairs Nothing
genThisMaybeIdentity :: Gen a -> Gen b -> Gen (This Maybe Identity a b)
genThisMaybeIdentity genA genB = Gen.choice [viaAlign, withLeft, withRight, justPairs, empty]
where
viaAlign = do
ma <- genMaybe genA
mb <- genMaybe genB
return $ align ma mb
withLeft = do
a <- genA
return $ This Nothing (Just (Left (Identity a)))
withRight = do
b <- genB
return $ This Nothing (Just (Right (Identity b)))
justPairs = do
pair <- (,) <$> genA <*> genB
return $ This (Just pair) Nothing
empty = return $ This Nothing Nothing
genThisNonEmptyNonEmpty :: Gen a -> Gen b -> Gen (This NonEmpty NonEmpty a b)
genThisNonEmptyNonEmpty genA genB = Gen.choice [viaAlign, withLeftover, withRightover, justPairs]
where
viaAlign = do
as <- genNonEmpty genA
bs <- genNonEmpty genB
return $ align as bs
withLeftover = do
pairs <- genNonEmpty ((,) <$> genA <*> genB)
leftover <- genNonEmpty genA
return $ This pairs (Just (Left leftover))
withRightover = do
pairs <- genNonEmpty ((,) <$> genA <*> genB)
rightover <- genNonEmpty genB
return $ This pairs (Just (Right rightover))
justPairs = do
pairs <- genNonEmpty ((,) <$> genA <*> genB)
return $ This pairs Nothing
genThisVectorNonEmpty :: Gen a -> Gen b -> Gen (This Vector.Vector NonEmpty a b)
genThisVectorNonEmpty genA genB = Gen.choice [viaAlign, withLeftover, withRightover, justPairs]
where
viaAlign = do
as <- genVector genA
bs <- genVector genB
return $ align as bs
withLeftover = do
pairs <- genVector ((,) <$> genA <*> genB)
leftover <- genNonEmpty genA
return $ This pairs (Just (Left leftover))
withRightover = do
pairs <- genVector ((,) <$> genA <*> genB)
rightover <- genNonEmpty genB
return $ This pairs (Just (Right rightover))
justPairs = do
pairs <- genVector ((,) <$> genA <*> genB)
return $ This pairs Nothing
genThisSeqNonEmpty :: Gen a -> Gen b -> Gen (This Seq.Seq NonEmpty a b)
genThisSeqNonEmpty genA genB = Gen.choice [viaAlign, withLeftover, withRightover, justPairs]
where
viaAlign = do
as <- genSeq genA
bs <- genSeq genB
return $ align as bs
withLeftover = do
pairs <- genSeq ((,) <$> genA <*> genB)
leftover <- genNonEmpty genA
return $ This pairs (Just (Left leftover))
withRightover = do
pairs <- genSeq ((,) <$> genA <*> genB)
rightover <- genNonEmpty genB
return $ This pairs (Just (Right rightover))
justPairs = do
pairs <- genSeq ((,) <$> genA <*> genB)
return $ This pairs Nothing
-- * Main test runner
main :: IO ()
main = do
hSetEncoding stdout utf8
hSetEncoding stderr utf8
results <-
sequence
[ checkGroup
"Functor laws - This"
[ ("identity (List)", prop_functorIdentity_List),
("composition (List)", prop_functorComposition_List)
],
checkGroup
"Bifunctor laws - This"
[ ("identity (List)", prop_bifunctorIdentity_List),
("composition (List)", prop_bifunctorComposition_List)
],
checkGroup
"Semialign laws - List"
[ ("naturality", prop_semialignNaturality_List),
("symmetry", prop_semialignSymmetry_List),
("coherence", prop_semialignCoherence_List),
("alignWith", prop_semialignWithLaw_List)
],
checkGroup
"Semialign laws - Maybe"
[ ("naturality", prop_semialignNaturality_Maybe),
("symmetry", prop_semialignSymmetry_Maybe),
("coherence", prop_semialignCoherence_Maybe)
],
checkGroup
"Semialign laws - NonEmpty"
[ ("naturality", prop_semialignNaturality_NonEmpty),
("symmetry", prop_semialignSymmetry_NonEmpty),
("coherence", prop_semialignCoherence_NonEmpty)
],
checkGroup
"Semialign laws - Vector"
[ ("naturality", prop_semialignNaturality_Vector),
("symmetry", prop_semialignSymmetry_Vector),
("coherence", prop_semialignCoherence_Vector)
],
checkGroup
"Semialign laws - Seq"
[ ("naturality", prop_semialignNaturality_Seq),
("symmetry", prop_semialignSymmetry_Seq),
("coherence", prop_semialignCoherence_Seq)
],
checkGroup
"Semialign laws - Map"
[ ("naturality", prop_semialignNaturality_Map),
-- symmetry skipped - known to fail for Map with disjoint keys
("coherence", prop_semialignCoherence_Map)
],
checkGroup
"Semialign laws - IntMap"
[ ("naturality", prop_semialignNaturality_IntMap),
-- symmetry skipped - known to fail for IntMap with disjoint keys
("coherence", prop_semialignCoherence_IntMap)
],
checkGroup
"Align laws - List"
[ ("right identity", prop_alignRightIdentity_List),
("left identity", prop_alignLeftIdentity_List),
("empty", prop_alignEmpty_List)
],
checkGroup
"Align laws - Maybe"
[ ("right identity", prop_alignRightIdentity_Maybe),
("left identity", prop_alignLeftIdentity_Maybe),
("empty", prop_alignEmpty_Maybe)
],
checkGroup
"Align laws - Vector"
[ ("right identity", prop_alignRightIdentity_Vector),
("left identity", prop_alignLeftIdentity_Vector),
("empty", prop_alignEmpty_Vector)
],
checkGroup
"Align laws - Seq"
[ ("right identity", prop_alignRightIdentity_Seq),
("left identity", prop_alignLeftIdentity_Seq),
("empty", prop_alignEmpty_Seq)
],
checkGroup
"Align laws - Map"
[ ("right identity", prop_alignRightIdentity_Map),
("left identity", prop_alignLeftIdentity_Map),
("empty", prop_alignEmpty_Map)
],
checkGroup
"Align laws - IntMap"
[ ("right identity", prop_alignRightIdentity_IntMap),
("left identity", prop_alignLeftIdentity_IntMap),
("empty", prop_alignEmpty_IntMap)
],
checkGroup
"Unalign laws - List"
[ ("roundtrip", prop_unalignRoundtrip_List),
("naturality", prop_unalignNaturality_List)
],
checkGroup
"Unalign laws - Maybe"
[ ("roundtrip", prop_unalignRoundtrip_Maybe),
("naturality", prop_unalignNaturality_Maybe)
],
checkGroup
"Unalign laws - NonEmpty"
[ ("roundtrip", prop_unalignRoundtrip_NonEmpty),
("naturality", prop_unalignNaturality_NonEmpty)
],
checkGroup
"Unalign laws - Vector"
[ ("roundtrip", prop_unalignRoundtrip_Vector),
("naturality", prop_unalignNaturality_Vector)
],
checkGroup
"Unalign laws - Seq"
[ ("roundtrip", prop_unalignRoundtrip_Seq),
("naturality", prop_unalignNaturality_Seq)
]
]
if and results
then exitSuccess
else exitFailure
checkGroup :: String -> [(String, Property)] -> IO Bool
checkGroup grpName props = do
putStrLn $ "\n━━━ " ++ grpName ++ " ━━━"
results <- mapM checkProp props
return $ and results
checkProp :: (String, Property) -> IO Bool
checkProp (name, prop) = do
putStrLn $ " • " ++ name
check prop