Tue Nov 6
We will begin with the definition of pseudo-random numbers from the notes.
The only difference is that we’re going to use an abbreviation for the type of the rand
function, by defining this type synonym:
Using Gen
can make it clearer when and where this state-passing pattern is being used. The first argument s
is the state type, and the second argument a
is the result type. Here is the definition of rand
using that type. (The code itself is exactly the same as in the notes.)
Now let’s make a simple enumerated type for representing coin flips. A coin flip can be head or tails:
This is ‘isomorphic’ to a Boolean value – we could just represent heads as True
and tails as False
(or vice-versa), but for clarity it’s sometimes helpful to declare such things as distinct types.
Next, let’s make a function to convert any integer into a coin flip. You want the coin to be fair (or as close as possible), so use even
or odd
to determine whether you produce Heads
or Tails
.
Now create a generator for coin flips. You can call rand
and/or coinFromInt
within your code.
In the notes we defined randomList
that would generate a list of specified length, containing random values. To do that, it hard-coded a call to rand
. Now we’ll implement a variant of that where the generator is specified as an argument. That way it can generate a list of anything for which a generator is provided. Here is the more general type:
So, given a generator (using state s
) of values of type a
, and an Int
for the length of the list, we can provide a generator of lists of values. The implementation looks a lot like the one in the notes – it has a case for zero, a case for n
, and then you thread the state around. The only difference is that you’re passing around the generator gen
as an argument, and use it instead of rand
.
iterGen _ 0 s0 = ([], s0)
iterGen g n s0 = (x:xs, s2)
where
(x, s1) = g s0
(xs, s2) = iterGen g (n-1) s1
Below are some usages of iterGen
. By providing different values, we can generate lists of different things. The state-threading to make use of pseudo-random numbers stays the same.
ghci> iterGen rand 5 (Seed 99)
([1663893,47762240,1728567349,872658027,1597634426],Seed {unSeed = 1597634426})
ghci> iterGen flipCoin 5 (Seed 99)
([Tails,Heads,Tails,Tails,Heads],Seed {unSeed = 1597634426})
You can even pass a partial application of iterGen
itself as the generator for another iterGen
. This is a list of lists of random numbers — essentially, a 3×5 matrix! (I added spacing to the output, for clarity.)
ghci> iterGen (iterGen rand 3) 5 (Seed 99)
([[ 1663893, 47762240, 1728567349],
[ 872658027, 1597634426, 1453759341],
[1411792268, 445832573, 537610028],
[1148037667, 2075984621, 906712338],
[ 570305654, 907610217, 628572478]],
Seed {unSeed = 628572478})
Here is an enumerated datatype representing five different colors.
Create a generator for colors, where each color is chosen with roughly equal probability. You may want to use mod
and toEnum
. And perhaps you want a separate function colorFromInt
that is analogous to coinFromInt
.
Some sample usage:
ghci> genColor (Seed 99)
(Green,Seed {unSeed = 1663893})
ghci> iterGen genColor 8 (Seed 99)
([Green,Red,Blue,Yellow,Orange,Orange,Green,Green],Seed {unSeed = 445832573})
Huh, it seemed to pick Green
a lot. Is this really a fair choice? Let’s generate many colors and count how many greens. It should be roughly one-fifth:
ghci> length $ filter (== Green) $ fst $ iterGen genColor 1000 (Seed 99)
204
So with this initial seed, 204 out of 1000 were green, which pretty close to 20%. We can try that with a few different seeds and see how much variation there is:
ghci> length $ filter (== Green) $ fst $ iterGen genColor 1000 (Seed 99)
204
ghci> length $ filter (== Green) $ fst $ iterGen genColor 1000 (Seed 29487)
208
ghci> length $ filter (== Green) $ fst $ iterGen genColor 1000 (Seed 474732)
193
ghci> length $ filter (== Green) $ fst $ iterGen genColor 1000 (Seed 59873)
222
The most extreme example was 222, but it’s still hovering around 20%.
Finally, I mentioned in class that the state-passing Gen
type can be a functor. For now, let’s not mess with making it an actual instance of the Functor
type class (I’ll explain why later), but we can still implement a function equivalent to fmap
:
To further understand this function, it can be helpful to expand the Gen
synonym, which would make it:
Here’s a stub for its definition:
Here it is in action, all with the same seed for comparison.
ghci> fmapGen (0-) rand (Seed 99)
(-1663893,Seed {unSeed = 1663893})
ghci> fmapGen show rand (Seed 99)
("1663893",Seed {unSeed = 1663893})
ghci> iterGen (fmapGen show rand) 5 (Seed 99)
(["1663893","47762240","1728567349","872658027","1597634426"],Seed {unSeed = 1597634426})
Or used to define some other generators:
Simulate rolling a 6-sided die a bunch of times:
ghci> iterGen diceRoll 50 (Seed 99)
([4,3,2,4,3,4,3,6,3,2,6,1,3,4,5,6,4,4,6,1,5,3,6,2,2,
6,1,3,2,1,4,4,6,6,1,2,2,2,2,4,4,6,5,5,6,3,5,1,5,3],
Seed {unSeed = 439652522})
Generate a selection of alphabetic passwords!
ghci> iterGen (iterGen alphabetic 12) 10 (Seed 99)
(["xgphyvqruxjm", "whonjjrgumxj",
"hfyupajxbjgt", "hzdvrdqmdouu",
"ukeaxhjumkmm", "hdglqrrohydr",
"mbvtuwstgkob", "yzjjtdtjroqp",
"yusfsbjlusek", "ghtfbdctvgmq"],
Seed {unSeed = 1959147024})
Or arbitrary passwords:
ghci> iterGen (iterGen anyChar 14) 10 (Seed 99)
(["TG9Bc*&f&R1%!H", "w$pB1%woPRRlsA",
"X%TH$f2^3ztBv$", "kI)ckW#cgI^B3M",
"8ua63x%xupL#HM", "x9KjV@Q&g7sgi^",
"&3Bttv^pDOY9&(", "CXCrrL%IcQMPPh",
"jDMz5QMyTs2XRM", "hntf5)exR9owxI"],
Seed {unSeed = 1528987498})
Leave these alone – these are some special-purpose generators used in my test cases.