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 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 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.