SmallCheck: Making types instance of typeclass Ser

2019-07-19 11:44发布

问题:

I'm trying to figure out how to use the smallcheck property-based test library in conjunction with tasty.

I ran into a problem with multi-field record types: How can I make a record type with mor than 4 fields member of the Serial typeclass?

I assume that this would be the normal way to go:

{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}

import Test.Tasty
import Test.Tasty.SmallCheck
import Test.SmallCheck.Series

data T1 = T1 { p1 :: Int,
               p2 :: Char,
               p3 :: [Int]
             } deriving (Show, Eq)

instance (Monad m) => Serial m T1 where
    series = cons3 T1 

main :: IO ()
main = defaultMain tests

tests :: TestTree
tests = testGroup "Tests" [scProps]

scProps = testGroup "(checked by SmallCheck)"
  [ testProperty "Test1" prop_test1 
  ]

prop_test1 x y = x == y
               where types = (x :: T1, y :: T1)

This works and of course 'Test1' fails. However this approach does not work for record types with more than 4 fields because the consN function is only defined to take up to 4 parameters. Here's a link to the module Test.SmallCheck.Series where the function is declared.

For something like:

data T1 = T1 { p1 :: Int,
               p2 :: Char,
               p3 :: Int,
               p4 :: Int,
               p5 :: [Int]
             } deriving (Show, Eq)

what is the way to add it to Serial? I tried using generics like this:

{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
{-# LANGUAGE DeriveGeneric #-}

import Test.Tasty
import Test.Tasty.SmallCheck
import Test.SmallCheck.Series
import GHC.Generics

data T1 = T1 { p1 :: Int,
               p2 :: Char,
               p3 :: [Int]
             } deriving (Show, Generic)

instance Serial m a => Serial m T1

But ghc refuses to accept the above example with the following message:

Variable occurs more often in a constraint than in the instance head
  in the constraint: Serial m a
  (Use -XUndecidableInstances to permit this)
  In the instance declaration for `Serial m T1'

Many thanks in advance!

jules

回答1:

Here's how e.g. cons3 is defined in SmallCheck:

cons3 :: (Serial m a, Serial m b, Serial m c) =>
         (a->b->c->d) -> Series m d
cons3 f = decDepth $
  f <$> series
    <~> series
    <~> series

So, by analogy, here's how you define the instance for your type with any number of fields:

instance Monad m => Serial m T1 where
  series = decDepth $
    T1 <$> series <~> series <~> series <~> series <~> series

(every series in the definition above corresponds to one field of your data structure).

Regarding your Generics code, it should look like this:

 instance Monad m => Serial m T1

There's no a here, so your constraint is superfluous (and actually leads to problems).