undefined
.
Eventually, the complete
version will be made available.
In class exercise: QuickCheck properties for lists
In this problem, you'll be writing QuickCheck properties to specify various list manipulation functions. For each property that you write, you should also include a buggy implementation of the tested function that does not satisfy the property you wrote. We'll be asking you to write your properties in a very particular way. For instance, suppose you are testing the const
function, which takes two arguments and returns the first one. Here's how you'd normally test (a type-restricted version of) it. First, we write an appropriate property:
Then we see what QuickCheck thinks of the property by entering this in GHCi:
*Main> quickCheck (prop_const :: Char -> Char -> Bool)
(The type annotation is needed because prop_const
is polymorphic; QuickCheck wouldn't know what type of test data to generate if we left it off.)
Below, we'll be asking you to fill in most of the definition and the type signature, given code snippets such as
where Undefined
is a dummy Testable type that will emit an error if used:
Filling in the property will then involve
- Adding parameters,
- Replacing the body, and
- Replacing the
Undefined
type with the desired type of the property. This might involve adding new constraints to the type variables or new arguments to the function type.
That will look, for instance, like so:
You'll fill in the types of all the parameters plus the return type, and you can add any type class constraints you want.
Notice that this property has an extra parameter const'
. When you write your tests this way, and use the passed in parameter (here const'
) instead of the real function (here const
), you will be able to test your buggy implementations as well. For instance, suppose that you have:
> constBug :: a -> a -> a
> constBug _ b = b -- Oops: this returns the *second* argument, not the first.
Then you'll be able to test your property on both const
(where it will pass) and constBug
(where it will not):
*Main> quickCheck (prop_const const :: Char -> Char -> Bool)
+++ OK, passed 100 tests.
*Main> quickCheck (prop_const constBug :: Char -> Char -> Bool)
*** Failed! Falsifiable (after 1 test and 2 shrinks):
'a'
'b'
-- Part a
Define a property showing that minimum
really returns the smallest element in a list; also, write a buggy implementation of minimum
that doesn't satisfy your property. (Don't forget to fill in the type signature for prop_minimum
!)
Be careful when testing your code with ghci. In particular, if you leave off the type annotation, you could get some strange results. For example, it is possible for this check to succeed even thouggh the property is correct and the buggy version is indeed buggy.
ghci> quickCheck $ prop_minimum minimumBug
The issue is that if prop_minimum minimumBug
has a polymorphic type, then ghci will default any remaining type variables to ()
(i.e. the unit type). This is a problem because any element of a list of unit values is the minimum element.
If you enable the warning about defaulting in ghci, it will alert you when this happens
ghci> :set -Wtype-defaults
ghci> quickCheck $ prop_minimum minimumBug
<interactive>:90:1-37: warning: [-Wtype-defaults]
• Defaulting the following constraints to type ‘()’
(Arbitrary a0)
arising from a use of ‘quickCheck’ at <interactive>:90:1-37
(Show a0)
arising from a use of ‘quickCheck’ at <interactive>:90:1-37
(Ord a0)
from a use of ‘prop_minimum’ at <interactive>:90:14-37
-- Part b
Define a property specifying the replicate
function from the standard library, and a buggy implementation that violates this spec. Recall that replicate k x
is a list containing k
copies of x
. One QuickCheck feature that will be important here (and later) is the use of newtype
s, such as NonNegative
, to restrict the domain of arbitrarily generated values:
ghci> sample (arbitrary :: Gen Int)
1
-2
4
-7
-9
-20
-1
22
60
-1780
3770
ghci> sample (arbitrary :: Gen NonNegative Int)
NonNegative {getNonNegative = 1}
NonNegative {getNonNegative = 0}
NonNegative {getNonNegative = 1}
NonNegative {getNonNegative = 8}
NonNegative {getNonNegative = 32}
NonNegative {getNonNegative = 5}
NonNegative {getNonNegative = 28}
NonNegative {getNonNegative = 23}
NonNegative {getNonNegative = 662}
NonNegative {getNonNegative = 584}
NonNegative {getNonNegative = 0}
However, simply using NonNegative Int
won't work here, as generating, say, a million-element list will take far too long (try it!).
So, your job is to define a newtype
for generating small non-negative integers (say, in the range 0 to 1000):
When you make a quickcheck instance, you should define both arbitrary
and shrink
for the SmallNonNegInt
type. Note: the shrink function in this instance can be derived from shrinking the int inside. Try it out first:
ghci> shrink (10 :: Int)
[0,5,8,9]
Then, use this type to define your property specifying replicate
.
-- Part c
Define two properties specifying group
; the first one should say that "the concatenation of the result is equal to the argument", and the second should say that "each sublist in the result is non-empty and contains only equal elements". Also write a buggy version of group
that violates both of them.
-- Part d
Write two interesting properties about reverse
. Write two different buggy versions, one which violates each property.
> prop_reverse_2 :: ([a] -> [a]) -> Undefined
> prop_reverse_2 reverse' = undefined
>
> reverseBug_1 :: [a] -> [a]
> reverseBug_1 = undefined
Once you've written all of these, evaluating main
in GHCi should produce the expected output:
> putStrLn "The following tests should all succeed:"
> qcName "const" $ prop_const (const :: Char -> Char -> Char)
> qcName "minimum" $ prop_minimum (minimum :: String -> Char)
> qcName "replicate" $ prop_replicate (replicate :: Int -> Char -> String)
> qcName "group_1" $ prop_group_1 (group :: String -> [String])
> qcName "group_2" $ prop_group_2 (group :: String -> [String])
> qcName "reverse_1" $ prop_reverse_1 (reverse :: String -> String)
> qcName "reverse_2" $ prop_reverse_2 (reverse :: String -> String)
> putStrLn "The following tests should all fail:"
> qcName "const" $ prop_const (constBug :: Char -> Char -> Char)
> qcName "minimum" $ prop_minimum (minimumBug :: String -> Char)
> qcName "replicate" $ prop_replicate (replicateBug :: Int -> Char -> String)
> qcName "group_1" $ prop_group_1 (groupBug :: String -> [String])
> qcName "group_2" $ prop_group_2 (groupBug :: String -> [String])
> qcName "reverse_1" $ prop_reverse_1 (reverseBug_1 :: String -> String)
> qcName "reverse_2" $ prop_reverse_2 (reverseBug_2 :: String -> String)