Lecture 16.0 — 2017-03-27
QuickCheck
This lecture is written in literate Haskell; you can download the raw source.
import Test.QuickCheck
import System.RandomWe spent class talking about QuickCheck. First, we saw the Gen monad for generating random values. The sample :: Show a => Gen a -> IO () and sample' :: Gen a -> IO [a] functions were useful here.
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]Next, we wrote some properties. for example, we can check that addition is commutative by taking the following property “for all n1 and n2”:
sums_ok :: Int -> Int -> Bool
sums_ok n1 n2 = n1 + n2 == n2 + n1and running quickCheck sums_ok.
Next we played with generating binary trees:
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]The Arbitrary type class is the piece of magic that lets quickCheck work, by giving it a default generator of random values.
instance Arbitrary a =>  Arbitrary (BST a) where
  arbitrary = btWe tried generating arbitrary binary search trees:
isBST :: Ord a => BST a -> Bool
isBST t = isBST' Nothing Nothing t
 where 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
       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
boundedBST :: (Arbitrary a, Ord a, Random a, Enum a) => a -> a -> Gen (BST a)
boundedBST lo hi | lo >= hi = return Empty
boundedBST lo hi =
  oneof [return Empty,
         do
           v <- choose (lo,hi)
           l <- boundedBST lo (pred v)
           r <- boundedBST (succ v) hi
           return $ Node l v r]
bsts :: (Arbitrary a, Bounded a, Ord a, Random a, Enum a) => Gen (BST a)
bsts = boundedBST minBound maxBoundWe can test it out:
all_ok = forAll (bsts :: Gen (BST Int)) isBSTAnd then we con go further, testing some real BST functions:
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 y 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
bsts' :: (Arbitrary a, Ord a) => Gen (BST a)
bsts' = listOf arbitrary >>= return . foldr insert Empty
insertFind :: Int -> BST Int -> Bool
insertFind x t = (find x $ insert x t) /= Nothing
insertValid :: Int -> Property
insertValid x = forAll bt $ \t -> isBST t ==> isBST $ insert x t
insertValid' :: Int -> Property
insertValid' x = forAll bsts $ \t -> isBST $ insert x t
insertValid'' :: Int -> Property
insertValid'' x = forAll bsts' $ \t -> isBST $ insert x t