Lecture 15.0 — 2016-10-20
Monads
This lecture is written in literate Haskell; you can download the raw source.
import Test.QuickCheck
import System.IO
import System.RandomWe started with a review of some of the midterm problems. If you still have questions, come on by my office.
Monads
We briefly saw how a monad instance for Parsers was helpful:
instance Monad Parser where
p >>= f = Parser $ \s ->
case parse p s of
Nothing -> Nothing
Just (v,s') -> parse (f v) s'
group :: Parser [Int]
group =
(spaces *> int) >>= \n ->
sequenceA $ replicate n (spaces *> int)
groups = some groupWe saw that the IO monad let us interact with the world:
main :: IO ()
main = do
putStr "What's your name? "
hFlush stdout -- skipped this line in lecture
s <- getLine
putStrLn $ "Hello, " ++ s ++ "!"QuickCheck
We spent the remainder of class talking about QuickCheck. First, we saw the Gen monad for generating random values. The sample :: Show a => Gen a -> IO () function was 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, Bounded a, Ord a, Random a, Enum a) => a -> a -> Gen (BST a)
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]But it didn’t work:
all_ok = forAll (boundedBST (minBound :: Int) maxBound) isBSTThe problem is that our tree gets so big that choose is picking bad values for us when the range gets too small. One solution to this is to size our trees to be small enough not to encounter this edge case (as in Tuesday’s lecture); a better thing to do would be to fix the way we call choose, but no time for that.