Generate psuedo-random numbers and values, by constructing
generators for them. There are a bunch of basic generators like
bool and int that you can build up into fancier generators
with functions like list and map.
You run a Generator by calling the generate function, which
also takes a random Seed, and passes back a new seed. You should
never use the same seed twice because you will get the same result! If you need
random values over time, you should store the most recent seed in your model. If
you have several independent models, you can split seeds into more
seeds.
This is an implementation of PCG by M. E. O'Neil, and is not cryptographically secure.
Generate a random value as specified by a given Generator, using a Seed
and returning a new one.
In the following example, we are trying to generate numbers between 0 and 100
with the int 0 100 generator. Each time we call generate we need to provide
a seed. This will produce a random number and a new seed to use if we want to
run other generators later.
(x, seed1) = generate (int 0 100) seed0
(y, seed2) = generate (int 0 100) seed1
(z, seed3) = generate (int 0 100) seed2
[x, y, z] -- [85, 0, 38]Notice that we use different seeds on each line. This is important! If you reuse the same seed, you get the same results.
(x, _) = generate (int 0 100) seed0
(y, _) = generate (int 0 100) seed0
(z, _) = generate (int 0 100) seed0
[x,y,z] -- [85, 85, 85]Of course, threading seeds through many calls to generate is tedious and
error-prone. That's why this library includes many functions to build more
complicated generators, allowing you to call generate only a small number of
times.
A Generator is like a recipe for generating certain random values. So a
Generator Int describes how to generate integers and a Generator String
describes how to generate strings.
Create a generator that produces boolean values. The following example simulates a coin flip that may land heads or tails.
type Flip = Heads | Tails
coinFlip : Generator Flip
coinFlip =
map (\b -> if b then Heads else Tails) boolGenerate 32-bit integers in a given range.
int 0 10 -- an integer between zero and ten
int -5 5 -- an integer between -5 and 5
int minInt maxInt -- an integer in the widest range feasibleThis function can produce values outside of the range [minInt,
maxInt] but sufficient randomness is not guaranteed.
Generate floats in a given range. The following example is a generator that produces decimals between 0 and 1.
probability : Generator Float
probability =
float 0 1Create a pair of random values. A common use of this might be to generate a point in a certain 2D space. Imagine we have a collage that is 400 pixels wide and 200 pixels tall.
randomPoint : Generator (Int,Int)
randomPoint =
pair (int -200 200) (int -100 100)Create a list of random values of a given length.
floatList : Generator (List Float)
floatList =
list 10 (float 0 1)
intList : Generator (List Int)
intList =
list 5 (int 0 100)
intPairs : Generator (List (Int, Int))
intPairs =
list 10 <| pair (int 0 100) (int 0 100)Create a generator that always produces the value provided. This is useful when creating complicated chained generators and you need to handle a simple case. It's also useful for the base case of recursive generators.
Transform the values produced by a generator. The following examples show how to generate booleans and letters based on a basic integer generator.
bool : Generator Bool
bool =
map ((==) 1) (int 0 1)
lowercaseLetter : Generator Char
lowercaseLetter =
map (\n -> Char.fromCode (n + 97)) (int 0 25)
uppercaseLetter : Generator Char
uppercaseLetter =
map (\n -> Char.fromCode (n + 65)) (int 0 25)Combine two generators.
This function is used to define things like pair where you want to
put two generators together.
pair : Generator a -> Generator b -> Generator (a,b)
pair genA genB =
map2 (,) genA genBCombine three generators. This could be used to produce random colors.
import Color
rgb : Generator Color.Color
rgb =
map3 Color.rgb (int 0 255) (int 0 255) (int 0 255)
hsl : Generator Color.Color
hsl =
map3 Color.hsl (map degrees (float 0 359.9999)) (float 0 1) (float 0 1)Combine four generators.
import Color
rgba : Generator Color.Color
rgba =
map4 Color.rgba (int 0 255) (int 0 255) (int 0 255) (float 0 1)Combine five generators.
Map over any number of generators.
randomPerson =
person `map` genFirstName
`andMap` genLastName
`andMap` genBirthday
`andMap` genPhoneNumberChain random operations by providing a callback that accepts a randomly-generated value. This all happens inside the generator, and the seed is managed for you.
In the following example, we will generate a random letter by putting together uppercase and lowercase letters.
letter : Generator Char
letter =
bool `andThen` \b ->
if b then genUppercaseLetter else genLowercaseLetterIf you find yourself calling constant in every branch of the callback, you can
probably use map instead.
Filter a generator so that all generated values satisfy the given predicate.
evens : Generator Int
evens =
filter (\i -> i % 2 == 0) (int minInt maxInt)If the predicate is unsatisfiable, the generator will not terminate, your application will crash with a stack overflow, and you will be sad. You should also avoid predicates that are merely very difficult to satisfy.
badCrashingGenerator =
filter (\_ -> False) anotherGenerator
likelyCrashingGenerator =
filter (\i -> i % 2000 == 0) (int minInt maxInt)Like initialSeed2, but takes only one integer. Mostly for compatibility
with core.
Split a seed into two new seeds. Each seed will generate different random numbers. This is useful when you have you need an unknown amount of randomness later but have to pass back a seed now.
Let's say you have have many independent components which will each want to generate many random numbers. After splitting a seed, you can pass one of the new seeds to a component, and keep the other to repeat the process.
makeComponents : Seed -> List (Seed -> Component) -> (List Component, Seed)
makeComponents seed constructors =
case constructors of
[] ->
([], seed)
c::cs ->
let
(seed1, seed2) = split seed
(tail, seed3) = makeComponents seed2 cs
in
(c seed1 :: tail, seed3)If you need a known number of seeds, you can obtain them like so:
generateNSeeds : Int -> Seed -> List Seed
generateNSeeds n seed =
let
helper seeds =
if List.length seeds >= n then
List.take n seeds
else
List.concatMap
(\seed -> let (a,b) = split seed in [a,b])
seeds
|> helper
in
helper [seed]Splitting is a reproducible operation; just like generating numbers, it will be the same every time. Similarly, once you split a seed, you must not reuse it.
Split seeds are extremely likely to be distinct for all practical purposes. However, it is not proven that there are no pathological cases.
Fast forward a seed the given number of steps, which may be negative (the
seed will be "rewound"). This allows a single seed to serve as a random-access
lookup table of random numbers. (To be sure no one else uses the seed,
split off your own).
diceRollTable : Int -> Int
diceRollTable i =
fastForward i mySeed |> generate (int 1 6) |> fstA Seed is the source of randomness in the whole system. It hides the
current state of the random number generator.
The maximum value for randomly generated 32-bit ints.
The minimum value for randomly generated 32-bit ints.
Take two integers to fully initialize the 64-bit state of the random number generator. Only the least significant 32 bits of each integer matter, and those bits should be as random as possible.
You can generate and copy random integers to create a reproducible psuedo-random generator.
Alternatively, you can generate the random integers on page load and pass them through a port. The program will be different every time.
Either way, you should initialize a random seed only once. After that, whenever you use a seed, you'll get another one back.