Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I test this applicative instance with checkers? (No instance for CoArbitrary (Validation e0 [Char]))

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.
like image 809
The Unfun Cat Avatar asked Jan 06 '23 11:01

The Unfun Cat


1 Answers

How to

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.


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 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.

like image 80
zakyggaps Avatar answered Jan 18 '23 22:01

zakyggaps