import Test.QuickCheck ----------------------------------------------------------------------- -- We have seen that `IO` is a type that represents side-effecting instructions. -- `IO` instructions can be built using do notation: ask :: IO () ask = do putStr "Type something: " s <- getLine putStrLn \$ "You typed " ++ show (length s) ++ " characters." -- Another instruction type is `Gen` which can be thought of as instructions for -- generating random numbers. Just like `IO`, `Gen` is an abstract type, but -- `quickCheck` knows how to use this type to generate random numbers. Just like -- for `IO`, we can use do notation and `return` to construct `Gen` values. -- `evenInteger` generates even integers -- (or, rather, holds instructions to generate even integers) evenInteger :: Gen Integer evenInteger = do n <- arbitrary return (2*n) test1 = sample evenInteger -- do notation does not restrict the instruction type. For example, `doTwice` -- from the IO lecture works for any instruction type: doTwice :: Monad m => m a -> m (a,a) doTwice instr = do a <- instr b <- instr return (a,b) -- `Monad` is the class of all "instruction-like" types. Both `IO` and `Gen` -- are members of this class. -- -- Some functions from the IO lecture have more general types: -- -- return :: Monad m => a -> m a -- sequence_ :: Monad m => [m a] -> m () -- sequence :: Monad m => [m a] -> m [a] test2 = sample \$ doTwice evenInteger doTwice_bad :: Monad m => m a -> m (a,a) doTwice_bad instr = do a <- instr return (a,a) test3 = sample \$ doTwice_bad evenInteger -- What's the difference? -- `evenIntegers` generates a list of even integers evenIntegers :: Int -> Gen [Integer] evenIntegers n = sequence \$ replicate n evenInteger ----------------------------------------------------------------------- data Suit = Spades | Hearts | Diamonds | Clubs deriving (Show,Eq) suit :: Gen Suit suit = oneof [ return Spades , return Hearts , return Diamonds , return Clubs ] -- `oneof` gives uniform distribution instance Arbitrary Suit where arbitrary = suit ----------------------------------------------------------------------- data Rank = Numeric Integer | Jack | Queen | King | Ace deriving (Show,Eq) rank_bad :: Gen Rank rank_bad = oneof [ return Jack , return Queen , return King , return Ace , do r <- choose (2,10) return (Numeric r) ] -- Use `sample` to see why it's bad rank :: Gen Rank rank = frequency [ (1, return Jack) , (1, return Queen) , (1, return King) , (1, return Ace) , (9, do r <- choose (2,10) return (Numeric r)) ] -- `frequency` gives the specified distribution of the cases instance Arbitrary Rank where arbitrary = rank -- Check that the arbitrary instance produces valid ranks. This is the -- *invariant* for the data type `Rank`. validRank :: Rank -> Bool validRank (Numeric r) = 2<=r && r<=10 validRank _ = True prop_rank r = collect r (validRank r) -- Change `Arbitrary` instance to use `rank_bad` and see how that affects the -- distribution ----------------------------------------------------------------------- data Card = Card Rank Suit deriving (Show,Eq) card :: Gen Card card = do r <- rank s <- suit return (Card r s) instance Arbitrary Card where arbitrary = card ----------------------------------------------------------------------- data Hand = Empty | Add Card Hand deriving (Eq, Show) hand_bad :: Gen Hand hand_bad = oneof [ return Empty , do c <- card h <- hand return (Add c h) ] hand :: Gen Hand hand = frequency [ (1, return Empty) , (4, do c <- card h <- hand return (Add c h)) ] instance Arbitrary Hand where arbitrary = hand size :: Hand -> Integer size Empty = 0 size (Add _ h) = 1 + size h prop_hand h = collect (size h) True -- Change `Arbitrary` instance to use `hand_bad` and see how that affects the -- distribution