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.
How to
To automatically derive an instance of
CoArbitrary
, your data type should have an instance ofGeneric
, which again can be automatically derived with some nice language extension:However the most significant mistake in your program is you were testing against
[]
but not your own type byapplicative [(Scss "b", Scss "a", Scss "c")]
. here's the definition ofapplicative
test bundle, details omitted:In short, given four types
m
,a
,b
andc
, this function will create a bunch of properties thatm
-- as an applicative functor -- should satisfy, and later you can test them with randoma
b
c
values generated byQuickCheck
.[(Scss "b", Scss "a", Scss "c")]
has type[(Validation String, Validation String, Validation String)]
makesm ~ []
.So you should provide some value of type
Validation e (a, b, c)
, or no value at all: You may have noticed theconst
right there in the definition ofapplicative
, only the type of the argument matters:After that you may run the test and get well-formatted result. But no, you shouldn't test an applicative in this way.
Why should not to
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 likeValidation 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 ApplicativeEDIT: 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.