Checkers is a library for reusable QuickCheck properties, particularly for standard type classes
How do I write a checkers instance to test whether my applicative instance of Validation is valid?
import Test.QuickCheck
import Test.QuickCheck.Checkers
import Test.QuickCheck.Classes
import Control.Applicative
import Data.Monoid
data Validation e a =
Error e
| Scss a
deriving (Eq, Show)
instance Functor (Validation e) where
fmap _ (Error e) = Error e
fmap f (Scss a) = Scss $ f a
instance Monoid e => Applicative (Validation e) where
pure = Scss
(<*>) (Scss f) (Scss a) = Scss $ f a
(<*>) (Error g) (Scss a) = Error g
(<*>) (Scss a) (Error g) = Error g
(<*>) (Error a) (Error g) = Error $ mappend a g
instance (Arbitrary a, Arbitrary b) => Arbitrary (Validation a b) where
arbitrary = do
a <- arbitrary
b <- arbitrary
elements [Scss a, Error b]
instance (Eq a, Eq b) => EqProp (Validation a b) where (=-=) = eq
main :: IO ()
main = quickBatch $ applicative [(Scss "b", Scss "a", Scss "c")]
I think I am almost there, but I get an error like:
chap17/Validation_applicative.hs:36:21: No instance for (CoArbitrary (Validation e0 [Char])) …
arising from a use of ‘applicative’
In the second argument of ‘($)’, namely
‘applicative [(Scss "b", Scss "a", Scss "c")]’
In the expression:
quickBatch $ applicative [(Scss "b", Scss "a", Scss "c")]
In an equation for ‘main’:
main = quickBatch $ applicative [(Scss "b", Scss "a", Scss "c")]
Compilation failed.
I have tried adding a CoArbitrary instance for Validation like so:
instance CoArbitrary (Validation a b)
but this leads to this error message:
chap17/Validation_applicative.hs:35:10: No instance for (GHC.Generics.Generic (Validation a b)) …
arising from a use of ‘Test.QuickCheck.Arbitrary.$gdmcoarbitrary’
In the expression: Test.QuickCheck.Arbitrary.$gdmcoarbitrary
In an equation for ‘coarbitrary’:
coarbitrary = Test.QuickCheck.Arbitrary.$gdmcoarbitrary
In the instance declaration for ‘CoArbitrary (Validation a b)’
chap17/Validation_applicative.hs:38:21: No instance for (Eq e0) arising from a use of ‘applicative’ …
The type variable ‘e0’ is ambiguous
Note: there are several potential instances:
instance Eq a => Eq (Const a b) -- Defined in ‘Control.Applicative’
instance Eq a => Eq (ZipList a) -- Defined in ‘Control.Applicative’
instance Eq a => Eq (Data.Complex.Complex a)
-- Defined in ‘Data.Complex’
...plus 65 others
In the second argument of ‘($)’, namely
‘applicative [(Scss "b", Scss "a", Scss "c")]’
In the expression:
quickBatch $ applicative [(Scss "b", Scss "a", Scss "c")]
In an equation for ‘main’:
main = quickBatch $ applicative [(Scss "b", Scss "a", Scss "c")]
Compilation failed.
To automatically derive an instance of CoArbitrary
, your data type should have an instance of Generic
, which again can be automatically derived with some nice language extension:
{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics
data Validation e a =
Error e
| Scss a
deriving (Eq, Show, Generic)
However the most significant mistake in your program is you were testing against []
but not your own type by applicative [(Scss "b", Scss "a", Scss "c")]
. here's the definition of applicative
test bundle, details omitted:
applicative :: forall m a b c.
( Applicative m
, Arbitrary a, CoArbitrary a, Arbitrary b, Arbitrary (m a)
, Arbitrary (m (b -> c)), Show (m (b -> c))
, Arbitrary (m (a -> b)), Show (m (a -> b))
, Show a, Show (m a)
, EqProp (m a), EqProp (m b), EqProp (m c)
) =>
m (a,b,c) -> TestBatch
applicative = const ( "applicative"
, [ ("identity" , property identityP)
, ("composition" , property compositionP)
, ("homomorphism", property homomorphismP)
, ("interchange" , property interchangeP)
, ("functor" , property functorP)
]
)
In short, given four types m
, a
, b
and c
, this function will create a bunch of properties that m
-- as an applicative functor -- should satisfy, and later you can test them with random a
b
c
values generated by QuickCheck
. [(Scss "b", Scss "a", Scss "c")]
has type [(Validation String, Validation String, Validation String)]
makes m ~ []
.
So you should provide some value of type Validation e (a, b, c)
, or no value at all: You may have noticed the const
right there in the definition of applicative
, only the type of the argument matters:
main :: IO ()
main = quickBatch $ applicative (undefined :: Validation String (Int, Double, Char))
After that you may run the test and get well-formatted result. But no, you shouldn't test an applicative in this way.
The test provided by checkers
is far from sufficient. By the runtime-monomorphic nature of GHC and how it treats ambiguity, you have to supply four concrete, non-polymorphic type to run the test like Validation String (Int, Double, Char)
, and the test module will generate and test against only those four types , while your applicative functor should work with any type that meets the context.
IMO most of the polymorphic functions do not fit well into a unit test framework: it cannot be tested against all possible types, so one has to choose either just do some test anyway with hand chosen types, or do the test on a type that's general enough (like Free monad when your code requires an arbitrary monad, but usually "general enough" is not well defined in other contexts).
You'd better rigorously examine your implementation and prove all the laws satisfied for all the cases, either with pen and paper or with some proving engine like agda. Here is an example on Maybe
that may help: Proving Composition Law for Maybe Applicative
EDIT: please read the comment. I'm not entirely understanding it but it means Integer
is the "general enough" type for unit testing polymorphic functions. I found This blog article by Bartosz Milewski and its bibliography good resources for grabbing the idea of parametricity and free theorem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With