Using the State monad for Random Generation
module RandomGen where
import System.Random (StdGen, mkStdGen, next) import State import Control.Monad hiding (liftM2)
Recall that quickCheck needs to randomly generate values of any type. It turns out that we can use the state monad to define the
Gen monad used in the QuickCheck libary.
First, a brief discussion of pseudo-random number generators. Pseudo-random number generators aren't really random, they just look like it. They are more like functions that are so complicated that they might as well be random. The nice property about them is that they are repeatable, if you give them the same seed they will produce the same sequence of "random" numbers.
Haskell has a library for Pseudo-Random numbers called System.Random.
type StdGen -- a type for a "standard" random number generator. -- | Construct a generator from a given seed. Distinct arguments -- are likely to produce distinct generators. mkStdGen :: Int -> StdGen -- | Returns an Int that is uniformly distributed in a range of at least 30 bits. next :: StdGen -> (Int, StdGen)
For example, we can generate a random integer by constructing a random number generator, calling
next and then projecting the result.
testRandom :: Int -> Int testRandom i = fst (next (mkStdGen i))
If we'd like to constrain that integer to a specific range (0,n) we can use
nextBounded :: Int -> StdGen -> (Int, StdGen) nextBounded bound s = (i `mod` bound, s') where (i, s') = next s
testBounded x = fst . nextBounded x . mkStdGen
QC is defined by class types that can construct random values. Let's do it first the hard way...
-- | Extract random values of any type class Arb1 a where arb1 :: StdGen -> (a, StdGen)
instance Arb1 Int where arb1 = next
instance Arb1 Bool where arb1 = \g -> let (i, g') = nextBounded 2 g in ( i == 0 , g')
testArb1 :: Arb1 a => Int -> a testArb1 i = fst (arb1 (mkStdGen i))
What about for pairs?
instance (Arb1 a, Arb1 b) => Arb1 (a,b) where arb1 = \s -> let (a, s') = arb1 s in let (b, s'') = arb1 s' in ((a,b), s'')
And for lists?
instance (Arb1 a) => Arb1 [a] where arb1 s = let (b, s') = nextBounded 5 s in if b == 1 then (, s') else let (a, s'') = arb1 s' in let (as, s''') = arb1 s'' in ((a:as), s''')
Ouch, there's a lot of state passing going on here.
State Monad to the Rescue
Last time, we developed a reusable library for the State monad. Let's use it to define a generator monad for QC.
Our reusable library defines an abstract type for the State monad, and the following operations for working with these sorts of computations.
type State s a = ... instance Monad (State s) where ... get :: State s s put :: s -> State s () runState :: State s a -> s -> (a,s)
Now let's define a type for generators, using the State monad.
type Gen a = State StdGen a
With this type, we can create a type class similar to the one in the QuickCheck library.
class Arb a where arb :: Gen a
For example, we can use the
state operation to inject the
next function into the
State StdGen a type.
instance Arb Int where arb = state next
bounded :: Int -> Gen Int bounded b = liftM (`mod` b) arb
What about random generation for other types? How does the state monad help that definition? How does it compare to the version above?
instance Arb Bool where arb = liftM (\ x -> (x `mod` 2) == 0) (arb :: Gen Int)
instance (Arb a, Arb b) => Arb (a,b) where arb = liftM2 (,) arb arb
liftM2 :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c liftM2 f = \ ma mb -> do a <- ma b <- mb return (f a b)
instance (Arb a) => Arb [a] where arb = do x <- (arb :: Gen Int) if ((x `mod` 5) == 1) then return  else (liftM2 (:) arb arb)
sample :: Show a => Gen a -> [a] sample gen = evalState (sequence (replicate 10 gen)) (mkStdGen 932497234)
-- sample :: Gen a => IO ()
Welcome to CIS 552!
See the home page for basic information about the course, the schedule for the lecture notes and assignments, the resources for links to the required software and online references, and the syllabus for detailed information about the course policies.