Question

I have a bunch of QuickCheck properties defined as follows:

 ...
 prop_scaleData3 d n = n > 1 ⇛ length (scaleData d n) ≡ n
 prop_scaleData4 d n = n > 1 ⇛ head (scaleData d n) ≡ -d
 prop_scaleData5 d n = n > 1 ⇛ last (scaleData d n) ≡ d 
 ...     

That's a lot of repetition. What would be the right way to DRY it up?

Was it helpful?

Solution

prop_scaleData3 d n = n > 1 ==> length (scaleData d n) == n
prop_scaleData4 d n = n > 1 ==> head (scaleData d n) == -d
prop_scaleData5 d n = n > 1 ==> last (scaleData d n) == d 

Just observe what is similar about these three functions and make a new helper function that extracts out the commonalities. For example:

scaleProp :: Int -> Int -> ([Int] -> Int) -> Int -> Bool
scaleProp d n op res = n > 1 ==> op (scaleData d n) == res

Then you can express your original props in terms of the helper:

 prop_scaleData3 d n = scaleProp d n length n
 prop_scaleData4 d n = scaleProp d n head   (-d)
 prop_scaleData4 d n = scapeProp d n last   d

At this point the repetition isn't about logic so much as syntax (naming functions and applying arguments). In such cases I don't feel the DRY principle is really helpful - you can make less syntactic repetition but you'll lose readability or modularity. For example, Toxaris combined the solutions into one function; we can do the same but lets do it in a simpler way with just lists of booleans:

prop_scaleData345 d n =
  let sp = scaleProp d n
  in and [sp length n, sp head (-d), sp last d]
  -- or instead:
  -- in all (uncurry sp) [(length, n), (head, negate d), (last, d)]

OTHER TIPS

What about something like

gt1 :: (Integer -> Prop) -> Prop
gt1 f = forAll $ \(Positive n) -> f $ n + 1

Then your properties become

prop_scaleData3 d = gt1 $ \n -> length (scaleData d n) ≡ n
prop_scaleData4 d = gt1 $ (≡ -d) . head . scaleData d
prop_scaleData5 d = gt1 $ (≡d)   . last . scaleData d

This avoids the duplicated logic. Whether or not you like the pointfree stuff is up to you :)

If you put this pragma at the top of your file:

{-# LANGUAGE ParallelListComp #-}

You might be able to do something like this with GHC:

prop_scaleData345 d n = n > 1 => conjoin
  [ f (scaleData d n) == x 
  | f <- [length, head, last]
  | x <- [n     , -d  , d   ]
  ]

This should generate a list of three properties, and then say that all of them have to be true. The first property uses f = length and x = n, the second property uses f = head and x = -d, and the last property uses f = last and x = d.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top