CS510AP Quickcheck Overview Simple Case
Total Page:16
File Type:pdf, Size:1020Kb
QuickCheck • Quick check is a Haskell library for doing random testing. CS510AP • You can read more about quickcheck at Quick Check – http://www.math.chalmers.se/~rjmh/QuickCheck/ Monads Overview Simple case • Programmers define properties they would like prop_bad xs = (reverse xs) == xs to be true about there programs where types = xs::[Int] • Programmers use default or user defined random test data generators Main> quickCheck prop_bad • The QuickCheck framework tests the properties Falsifiable, after 7 tests: against a large number of random inputs [0,4,-6,-2] • It reports the success and failure • QuickCheck is in some sense orthogonal to the kind of testing done in HUnit. A useful case Quantified tests prop_RevId xs = reverse (reverse xs) == xs <condition> ==> <property> where types = xs::[Int] Main> quickCheck prop_RevId prop_RevLong xs = OK, passed 100 tests. length xs > 2 ==> reverse (reverse xs) == xs where types = xs::[Int] Main> quickCheck prop_RevLong OK, passed 100 tests. Main> quickCheck prop_RevLong3 OK, passed 100 But Collecting data tests. 27% 3. 10% 5. prop_RevLong xs = collect <expression>$ <property> 8% 6. 6% 9. length xs > 4 ==> 5% 7. 5% 4. reverse (reverse xs) == xs prop_RevLong3 xs = 5% 10. 4% 8. where types = xs::[Int] length xs > 2 ==> 4% 16. 4% 11. collect (length xs) $ 3% 17. 3% 14. reverse (reverse xs) == xs 2% 24. Main> quickCheck prop_RevLong 2% 13. where types = xs::[Int] 2% 12. Arguments exhausted after 0 tests. 1% 44. 1% 40. 1% 36. 1% 31. 1% 28. 1% 27. 1% 26. 1% 21. 1% 18. 1% 15. A short side-trip Monads Another quantified test ordered [] = True ordered [x] = True • Monads & Actions ordered (x:y:zs) = (x <= y) && (ordered (y:zs)) • A Monad is an Abstract Data Type insert x [] = [x] (ADT) insert x (y:ys) = if x<=y then x:y:ys else y:(insert x ys) • A Monad encapsulates effects –i.e. hides their implementation prop_Insert x xs = ordered xs ==> ordered (insert x xs) • A Monad uses types to separates where types = x::Int Actions from pure computations Main> quickCheck prop_Insert • A Monad controls the order of effects OK, passed 100 tests. Actions A monad is an ADT • It is sometimes useful to use actions or side effects in a functional program . • A monad is an abstract datatype – Update a piece of state (assignment) – do some IO – draw graphics on a display –It abstracts computations with effects. – return an exception rather than an answer –It hides the details of its implementation. – generate random test cases –It exports an abstract interface. • How do we do this? –It specifies the order in which effects occur. – We use a Monad – A Monad is a type constructor which has an implied –It separates pure computations from effectfull action. ones using the type system. – For example: IO Int is an action which performs –It can have multiple implementations. some IO and then returns an Int A Monad uses types to separate Actions Monads Encapsulate Effects (with effects) from pure computations • A monad captures the meaning and use of computations which have effects on the real • A Monad is a type constructor which has an world. implied action. • A monad describes or specifies the effects in a • For example: “purely functional” way. – a computation with type (IO Int ) • A monad allows one to reason about effects, – an action which might perform some IO and which using the simple rule of substitution of equals then returns an Int for equals. • Computations with non-monadic types cannot have any effects. They are pure computations. • A monad needn’t be implemented this way The user (and the compiler) can rely on this. A monad orders effects Typing the Do If a variable is bound, it • A Monad specifies which effects come before has type a, and its scope others. is to the end of the do Each action • The Do operator provides this control over the do { var <- location x must have a order in which computations occur monadic type: ; write var (b+1) (M a) do { var <- location x -- the first action ; write var (b+1) -- the next action ; return (b+1) } } The very last action cannot have a binding A monad’s interface hides An Example: IO actions details • A monad has an interface that hides the details • Each function performs an action of doing some IO of its implementation – ? :t getChar -- get 1 char from stdin – getChar :: IO Char • Every monad has at least the two operations – ? :t putChar -- put Char to stdout – putChar :: Char -> IO() – Do Lets users control the order of actions – Return : a -> Action a makes an empty action – ? :t getLine -- get a whole line from stdin – getLine :: IO String • Each monad has its own additional operations. – ? :t readFile -- read a file into a String – readFile :: FilePath -> IO String – ? :t writeFile -- write a String to a file – writeFile :: FilePath -> String -> IO () Io operations Example: Combining IO actions getCharX :: Io Char -- get 1 char from stdin getCharX = Io(\ (c:cs) -> (c,cs,"")) getLineX :: Io [Char] getLineX = putCharX :: Char -> Io() -- put Char to stdout do { c <- getCharX -- get a char putCharX c = Io(\ s -> ((),s,[c])) ; if c == '\n' -- if its newline then return "" -- no-op action which getLineX :: Io String -- get a whole line from stdin -- returns empty string -- recursively getLineX = do { c <- getCharX else do { l <- getLine' -- get a line ; if c == '\n' ; return (c:l) -- no-op action then return "" } -- to cons c to l else do { cs <- getLineX } ; return (c:cs) } } Potentially reads a string from stdin, if it is ever performed Observations Back to Defining Generators • Actions are first class. – They can be abstracted (param’s of functions) – Stored in data structures. -- It is possible to have a list of actions, choose :: Random a => (a,a) -> Gen a etc. oneof :: [Gen a] -> Gen a • Actions can be composed. – They can be built out of smaller actions by glueing them together with do and return – They are sequenced with do much like one uses semi-colon in list:: Gen [Int] languages like Pascal and C. list = do { elem <- choose (1,100) • Actions can be performed (run). ; tail <- list – separation of construction from performance is key to their versatility. ; oneof [ return [] , return(elem:tail) ] • Actions of type: Action () are like statements in imperative languages. } – They are used only for their side effects. Note: Gen is a Monad Generating Ordered lists • Gen is a Monad orderedList:: Int -> Gen [Int] orderedList n = do { elem <- choose (1,n) • A program with type (Gen a) produces an ; tail <- orderedList elem object of type ‘a’ while performing a possible ‘Gen’ action, generating random ; oneof [ return [] bits of data. , return(elem:tail) ] } Controlling choice using Monads better frequency :: [(Int,Gen a)] -> Gen a lift :: Monad m => (b -> c) -> m b -> m c lift f x = do { a <- x; return(f a)} list2:: Gen [Int] list2 = do { elem <- choose (1,100) lift2 :: Monad m => ; tail <- list (b -> c -> d) -> m b -> m c -> m d ; frequency lift2 f x y = [ (1,return []) do { a <- x; b <- y; return(f a b)} , (4,return(elem:tail)) ] } Another list generator Generating Random RE list3:: Gen [Int] data RE list3 = = Empty frequency | Union RE RE [ (1,return []) | Concat RE RE , (4,lift2 (:) (choose (1,100)) list3) ] | Star RE | C Char Using generators re:: Gen RE forAll <generator> re = $ \<pattern> -> <property> frequency [ (1,return Empty) , (6,lift C (choose ('a','z'))) prop_re = , (3,lift2 Union re re) forAll re $ \ re -> all (reAsPred re) , (3,lift2 Concat re re) (reAsSet re) , (1,lift Star re) ] Controlling the test generation The sized function re2:: Int -> Int -> Gen RE re2 star 0 = frequency [(1,return Empty) • Test generation is controlled by the library. ,(3,lift C (choose ('a','z')))] re2 star d = • Test sizes start out small, and increase in frequency [ (3,return Empty) size as the number of tests increase. , (8,lift C (choose ('a','z'))) , (4,lift2 Union (re2 star (d-1)) (re2 star (d-1))) • Users can control this with the function , (4,lift2 Concat (re2 star (d-1)) (re2 star (d-1))) , (star,lift Star (re2 0 (d-1))) sized. ] • sized :: (Int -> Gen a) -> Gen a Using sized Installing default generators re3:: Int -> Int -> Gen RE class Arbitrary a where re3 star 0 = frequency [(1,return Empty) ,(3,lift C (choose ('a','z')))] arbitrary :: Gen a re3 star d = frequency coarbitrary :: a -> Gen b -> Gen b [ (3,return Empty) , (8,lift C (choose ('a','z'))) , (4,lift2 Union (re3 star (d `div` 2)) (re3 star (d`div` 2))) , (4,lift2 Concat (re3 star (d`div` 2)) (re3 star (d`div` 2))) , (star,lift Star (re3 0 (d`div` 2))) ] instance Arbitrary RE where prop_re3 = arbitrary = sized (re3 1) forAll (sized (re3 1)) $ \ r -> all (reAsPred r) (reAsSet r).