Lecture 12 — 2018-02-22
QuickCheck
This lecture is written in literate Haskell; you can download the raw source.
Generators
import Test.QuickCheck
import System.Random
evenBelowTen :: Gen Int
evenBelowTen = elements [0,2,4,6,8]
fiveNums :: Gen [Int]
fiveNums = do
n1 <- evenBelowTen
n2 <- evenBelowTen
n3 <- evenBelowTen
n4 <- evenBelowTen
n5 <- evenBelowTen
return [n1,n2,n3,n4,n5]
data BST a = Empty | Node (BST a) a (BST a) deriving (Eq, Show)
bt :: Arbitrary a => Gen (BST a)
bt = oneof [return Empty,
Node <$> bt <*> arbitrary <*> bt]
instance Arbitrary a => Arbitrary (BST a) where
arbitrary = bt
Testing properties
Check out the Testable
type class.
sums_ok :: Int -> Int -> Bool
sums_ok n1 n2 = n1 + n2 == n2 + n1
sums_ok = forAll (arbitrary :: (Int,Int)) $ sum_ok
where sum_ok :: Int -> Int -> Bool
sum_ok (n1,n2) = n1 + n2 == n2 + n1
Size-bounded binary trees
size :: BST a -> Int
size Empty = 0
size (Node l _ r) = 1 + size l + size r
sizedBT :: Arbitrary a => Int -> Gen (BST a)
sizedBT 0 = return Empty
sizedBT n = oneof
[return Empty,
Node <$> sizedBT (n `div` 2)
<*> arbitrary
<*> sizedBT (n `div` 2)]
bt' :: Arbitrary a => Gen (BST a)
bt' = sized sizedBT
prop_nonnegative_size :: BST a -> Bool
prop_nonnegative_size t = size t > 0
Generating BSTs
Here are the familiar BST operations.
insert x Empty = Node Empty x Empty
insert x (Node l y r) | x < y = Node (insert x l) y r
| x == y = Node l x r
| x > y = Node l y (insert x r)
delete x Empty = Empty
delete x (Node l y r)
| x < y = Node (delete x l) y r
| x > y = Node l y (delete x r)
| x == y = case (l,r) of
(Empty, _) -> r
(l, Empty) -> l
_ -> let (min, r') = deleteMin r in
Node l min r'
where deleteMin Empty = error "nope"
deleteMin (Node Empty x r) = (x,r)
deleteMin (Node l x r) =
let (min, l') = deleteMin l in
(min, Node l' x r)
find _ Empty = Nothing
find v (Node l x r)
| v < x = v `find` l
| v == x = Just x
| v > x = v `find` r
We can directly generate BSTs by keeping track of their bounds.
boundedBST :: (Ord a, Enum a, Random a) => a -> a -> Gen (BST a)
boundedBST lo hi
| hi < lo = return Empty -- critical case!!!
| otherwise =
oneof [return Empty,
do
v <- choose (lo,hi)
l <- boundedBST lo (pred v)
r <- boundedBST (succ v) hi
return (Node l v r)]
maybeBounded :: Ord a => Maybe a -> Maybe a -> a -> Bool
maybeBounded Nothing Nothing x = True
maybeBounded Nothing (Just upper) x = x < upper
maybeBounded (Just lower) Nothing x = lower < x
maybeBounded (Just lower) (Just upper) x = lower < x && x < upper
isBST' :: Ord a => Maybe a -> Maybe a -> BST a -> Bool
isBST' lower upper Empty = True
isBST' lower upper (Node l x r) =
maybeBounded lower upper x &&
isBST' lower (Just x) l &&
isBST' (Just x) upper r
isBST :: Ord a => BST a -> Bool
isBST t = isBST' Nothing Nothing t
One possible instance for Arbitrary
BSTs:
instance (Ord a, Bounded a, Enum a, Random a) => Arbitrary (BST a) where
arbitrary = boundedBST minBound maxBound
But we can do better: just reuse our BST operations to generate them.
bsts :: (Arbitrary a, Ord a) => Gen (BST a)
bsts = do
vs <- listOf arbitrary
return $ foldr insert Empty vs
instance (Arbitrary a, Ord a) => Arbitrary (BST a) where
arbitrary = bsts
prop_insertFind :: Int -> BST Int -> Bool
prop_insertFind x t = find x (insert x t) == Just x
prop_insertFind' :: Property
prop_insertFind' =
forAll arbitrary $ \x ->
forAll bsts $ \t ->
find x (insert x t) == Just (x::Int)