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.Random
We 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 Parser
s 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 group
We 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 + n1
and 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 = bt
We 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) isBST
The 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.