quickcheck-classes 0.4.5 → 0.4.6
raw patch · 5 files changed
+138/−8 lines, 5 files
Files
- changelog.md +9/−0
- quickcheck-classes.cabal +1/−1
- src/Test/QuickCheck/Classes.hs +63/−5
- src/Test/QuickCheck/Classes/IsList.hs +61/−1
- test/Spec.hs +4/−1
changelog.md view
@@ -4,6 +4,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to the [Haskell Package Versioning Policy](https://pvp.haskell.org/). +## [0.4.6] - 2018-03-29+### Added+- Property test the naturality law for `MonadZip`. There is another law+ that instances should satisfy (the Information Preservation law), but+ it's more difficult to write a test for. It has been omitted for now.+- Property tests for all `MonadPlus` laws.+- Several additional property tests for list-like containers: mapMaybe,+ replicate, filter.+ ## [0.4.5] - 2018-03-26 ### Added - Property tests for list-like containers that have `IsList` instances.
quickcheck-classes.cabal view
@@ -1,5 +1,5 @@ name: quickcheck-classes-version: 0.4.5+version: 0.4.6 synopsis: QuickCheck common typeclasses description: This library provides quickcheck properties to
src/Test/QuickCheck/Classes.hs view
@@ -69,6 +69,8 @@ , traversableLaws , functorLaws , monadLaws+ , monadPlusLaws + , monadZipLaws #endif #if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,5,0) , bifunctorLaws @@ -100,6 +102,8 @@ import Test.QuickCheck hiding ((.&.)) import Test.QuickCheck.Property (Property(..)) import Control.Monad.Primitive (PrimMonad,PrimState,primitive,primitive_)+import Control.Monad.Zip (MonadZip(mzip))+import Control.Arrow ((***)) import qualified Control.Monad.Trans.Writer.Lazy as WL import qualified Data.Primitive as P import qualified Data.Semigroup as SG@@ -128,7 +132,7 @@ #if MIN_VERSION_QuickCheck(2,10,0) import Control.Exception (ErrorCall,try,evaluate)-import Control.Monad (ap)+import Control.Monad (ap,liftM,MonadPlus(mzero,mplus)) import Control.Monad.Trans.Class (lift) #if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0) import Data.Functor.Classes@@ -805,6 +809,27 @@ , ("Associativity", alternativeAssociativity p) ] +-- | Tests the following monad plus properties:+--+-- [/Left Identity/]+-- @'mplus' 'empty' x ≡ x@+-- [/Right Identity/]+-- @'mplus' x 'empty' ≡ x@+-- [/Associativity/]+-- @'mplus' a ('mplus' b c) ≡ 'mplus' ('mplus' a b) c)@ +-- [/Left Zero/]+-- @'mzero' '>>=' f ≡ 'mzero'@+-- [/Right Zero/]+-- @m >> 'mzero' ≡ 'mzero'@+monadPlusLaws :: (MonadPlus f, Eq1 f, Show1 f, Arbitrary1 f) => proxy f -> Laws+monadPlusLaws p = Laws "MonadPlus"+ [ ("Left Identity", monadPlusLeftIdentity p)+ , ("Right Identity", monadPlusRightIdentity p)+ , ("Associativity", monadPlusAssociativity p)+ , ("Left Zero", monadPlusLeftZero p)+ , ("Right Zero", monadPlusRightZero p)+ ]+ -- | Tests the following applicative properties: -- -- [/Identity/]@@ -869,6 +894,18 @@ , ("Ap", monadAp p) ] +-- | Tests the following monadic zipping properties:+--+-- [/Naturality/]+-- @liftM (f *** g) (mzip ma mb) = mzip (liftM f ma) (liftM g mb)@+--+-- In the laws above, the infix function @***@ refers to a typeclass+-- method of 'Arrow'.+monadZipLaws :: (MonadZip f, Applicative f, Eq1 f, Show1 f, Arbitrary1 f) => proxy f -> Laws+monadZipLaws p = Laws "MonadZip"+ [ ("Naturality", monadZipNaturality p)+ ]+ -- | Tests the following 'Foldable' properties: -- -- [/fold/]@@ -1220,13 +1257,13 @@ data LinearEquationM m = LinearEquationM (m LinearEquation) (m LinearEquation) -runLinearEquation :: Integer -> LinearEquation -> Integer-runLinearEquation x (LinearEquation a b) = a * x + b+runLinearEquation :: LinearEquation -> Integer -> Integer+runLinearEquation (LinearEquation a b) x = a * x + b runLinearEquationM :: Functor m => LinearEquationM m -> Integer -> m Integer runLinearEquationM (LinearEquationM e1 e2) i = if odd i- then fmap (runLinearEquation i) e1- else fmap (runLinearEquation i) e2+ then fmap (flip runLinearEquation i) e1+ else fmap (flip runLinearEquation i) e2 instance Eq1 m => Eq (LinearEquationM m) where LinearEquationM a1 b1 == LinearEquationM a2 b2 = eq1 a1 a2 && eq1 b1 b2@@ -1337,6 +1374,21 @@ alternativeAssociativity :: forall proxy f. (Alternative f, Eq1 f, Show1 f, Arbitrary1 f) => proxy f -> Property alternativeAssociativity _ = property $ \(Apply (a :: f Integer)) (Apply (b :: f Integer)) (Apply (c :: f Integer)) -> eq1 (a <|> (b <|> c)) ((a <|> b) <|> c) +monadPlusLeftIdentity :: forall proxy f. (MonadPlus f, Eq1 f, Show1 f, Arbitrary1 f) => proxy f -> Property+monadPlusLeftIdentity _ = property $ \(Apply (a :: f Integer)) -> eq1 (mplus mzero a) a++monadPlusRightIdentity :: forall proxy f. (MonadPlus f, Eq1 f, Show1 f, Arbitrary1 f) => proxy f -> Property+monadPlusRightIdentity _ = property $ \(Apply (a :: f Integer)) -> eq1 (mplus a mzero) a++monadPlusAssociativity :: forall proxy f. (MonadPlus f, Eq1 f, Show1 f, Arbitrary1 f) => proxy f -> Property+monadPlusAssociativity _ = property $ \(Apply (a :: f Integer)) (Apply (b :: f Integer)) (Apply (c :: f Integer)) -> eq1 (mplus a (mplus b c)) (mplus (mplus a b) c)++monadPlusLeftZero :: forall proxy f. (MonadPlus f, Eq1 f, Show1 f, Arbitrary1 f) => proxy f -> Property+monadPlusLeftZero _ = property $ \(k' :: LinearEquationM f) -> eq1 (mzero >>= runLinearEquationM k') mzero++monadPlusRightZero :: forall proxy f. (MonadPlus f, Eq1 f, Show1 f, Arbitrary1 f) => proxy f -> Property+monadPlusRightZero _ = property $ \(Apply (a :: f Integer)) -> eq1 (a >> (mzero :: f Integer)) mzero+ applicativeIdentity :: forall proxy f. (Applicative f, Eq1 f, Show1 f, Arbitrary1 f) => proxy f -> Property applicativeIdentity _ = property $ \(Apply (a :: f Integer)) -> eq1 (pure id <*> a) a @@ -1365,6 +1417,12 @@ monadLeftIdentity _ = property $ \(k' :: LinearEquationM f) (a :: Integer) -> let k = runLinearEquationM k' in eq1 (return a >>= k) (k a)++monadZipNaturality :: forall proxy f. (MonadZip f, Functor f, Eq1 f, Show1 f, Arbitrary1 f) => proxy f -> Property+monadZipNaturality _ = property $ \(f' :: LinearEquation) (g' :: LinearEquation) (Apply (ma :: f Integer)) (Apply (mb :: f Integer)) -> + let f = runLinearEquation f'+ g = runLinearEquation g'+ in eq1 (liftM (f *** g) (mzip ma mb)) (mzip (liftM f ma) (liftM g mb)) monadRightIdentity :: forall proxy f. (Monad f, Eq1 f, Show1 f, Arbitrary1 f) => proxy f -> Property monadRightIdentity _ = property $ \(Apply (m :: f Integer)) ->
src/Test/QuickCheck/Classes/IsList.hs view
@@ -32,14 +32,21 @@ , traverseProp , generateProp , generateMProp+ , replicateProp+ , replicateMProp+ , filterProp+ , filterMProp+ , mapMaybeProp+ , mapMaybeMProp #endif ) where #if MIN_VERSION_base(4,7,0) import Control.Monad.ST (ST,runST)-import Control.Monad (mapM)+import Control.Monad (mapM,filterM,replicateM) import Control.Applicative (liftA2) import GHC.Exts (IsList,Item,toList,fromList)+import Data.Maybe (mapMaybe,catMaybes) import Data.Proxy (Proxy) import Data.Foldable (foldlM) import Test.QuickCheck (Property,Arbitrary,Function,CoArbitrary,(===),property,@@ -117,6 +124,56 @@ generateMProp _ f = property $ \(NonNegative len) func -> fromList (runST (stGenerateList len (stApplyFun func))) === runST (f len (stApplyFun func)) +replicateProp :: (Item c ~ a, Eq c, Show c, IsList c, Arbitrary a, Show a)+ => Proxy a -- ^ input element type+ -> (Int -> a -> c) -- replicate function+ -> Property+replicateProp _ f = property $ \(NonNegative len) a ->+ fromList (replicate len a) === f len a++replicateMProp :: (Item c ~ a, Eq c, Show c, IsList c, Arbitrary a, Show a)+ => Proxy a -- ^ input element type+ -> (forall s. Int -> ST s a -> ST s c) -- replicate function+ -> Property+replicateMProp _ f = property $ \(NonNegative len) a ->+ fromList (runST (replicateM len (return a))) === runST (f len (return a))++-- | Property for the @filter@ function, which keeps elements for which+-- the predicate holds true.+filterProp :: (IsList c, Item c ~ a, Arbitrary c, Show c, Show a, Eq c, CoArbitrary a, Function a)+ => Proxy a -- ^ element type+ -> ((a -> Bool) -> c -> c) -- ^ map function+ -> Property+filterProp _ f = property $ \c func ->+ fromList (filter (applyFun func) (toList c)) === f (applyFun func) c++-- | Property for the @filterM@ function, which keeps elements for which+-- the predicate holds true in an applicative context.+filterMProp :: (IsList c, Item c ~ a, Arbitrary c, Show c, Show a, Eq c, CoArbitrary a, Function a)+ => Proxy a -- ^ element type+ -> (forall s. (a -> ST s Bool) -> c -> ST s c) -- ^ traverse function+ -> Property+filterMProp _ f = property $ \c func ->+ fromList (runST (filterM (return . applyFun func) (toList c))) === runST (f (return . applyFun func) c)++-- | Property for the @mapMaybe@ function, which keeps elements for which+-- the predicate holds true.+mapMaybeProp :: (IsList c, Item c ~ a, Item d ~ b, Eq d, IsList d, Arbitrary b, Show d, Show b, Arbitrary c, Show c, Show a, Eq c, CoArbitrary a, Function a)+ => Proxy a -- ^ input element type+ -> Proxy b -- ^ output element type+ -> ((a -> Maybe b) -> c -> d) -- ^ map function+ -> Property+mapMaybeProp _ _ f = property $ \c func ->+ fromList (mapMaybe (applyFun func) (toList c)) === f (applyFun func) c++mapMaybeMProp :: (IsList c, IsList d, Eq d, Show d, Show b, Item c ~ a, Item d ~ b, Arbitrary c, Arbitrary b, Show c, Show a, CoArbitrary a, Function a)+ => Proxy a -- ^ input element type+ -> Proxy b -- ^ output element type+ -> (forall s. (a -> ST s (Maybe b)) -> c -> ST s d) -- ^ traverse function+ -> Property+mapMaybeMProp _ _ f = property $ \c func ->+ fromList (runST (mapMaybeMList (return . applyFun func) (toList c))) === runST (f (return . applyFun func) c)+ imapList :: (Int -> a -> b) -> [a] -> [b] imapList f xs = map (uncurry f) (zip (enumFrom 0) xs) @@ -124,6 +181,9 @@ imapMList f = go 0 where go !_ [] = return [] go !ix (x : xs) = liftA2 (:) (f ix x) (go (ix + 1) xs)++mapMaybeMList :: Applicative f => (a -> f (Maybe b)) -> [a] -> f [b]+mapMaybeMList f = fmap catMaybes . traverse f generateList :: Int -> (Int -> a) -> [a] generateList len f = go 0 where
test/Spec.hs view
@@ -5,6 +5,7 @@ {-# LANGUAGE ScopedTypeVariables #-} import Control.Monad+import Control.Monad.Zip (MonadZip) import Control.Applicative #if defined(VERSION_aeson) import Data.Aeson (ToJSON,FromJSON)@@ -88,11 +89,13 @@ #if MIN_VERSION_QuickCheck(2,10,0) #if MIN_VERSION_base(4,9,0) || MIN_VERSION_transformers(0,4,0)-allHigherLaws :: (Traversable f, Monad f, Applicative f, Eq1 f, Arbitrary1 f, Show1 f) => proxy f -> [Laws]+allHigherLaws :: (Traversable f, MonadZip f, MonadPlus f, Applicative f, Eq1 f, Arbitrary1 f, Show1 f) => proxy f -> [Laws] allHigherLaws p = [ functorLaws p , applicativeLaws p , monadLaws p+ , monadPlusLaws p+ , monadZipLaws p , foldableLaws p , traversableLaws p ]