mgold/elm-random-pcg/1.2.0

NOTE: this package is not compatible with Elm 0.19.1

Random.PCG

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.

Getting Started

initialSeed2 : Int -> Int -> Seed

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.

$ node
> Math.floor(Math.random()*0xFFFFFFFF)
227852860
> Math.floor(Math.random()*0xFFFFFFFF)
1498709020


-- Elm
seed0 : Seed
seed0 = initialSeed2 227852860 1498709020

Alternatively, you can generate the random integers on page load and pass them through a port. The program will be different every time.

-- Elm
port randomSeed : (Int, Int)


seed0 : Seed
seed0 = (uncurry initialSeed2) randomSeed


-- JS
Elm.fullscreen(Elm.ModuleName,
  {randomSeed: [Math.floor(Math.random()*0xFFFFFFFF),
                Math.floor(Math.random()*0xFFFFFFFF)] })

Either way, you should initialize a random seed only once. After that, whenever you use a seed, you'll get another one back.

generate : Generator a -> Seed -> ( a, Seed )

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.

Basic Generators

type Generator a

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.

bool : Generator Bool

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) bool
int : Int -> Int -> Generator Int

Generate 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 feasible

This function can produce values outside of the range [minInt, maxInt] but sufficient randomness is not guaranteed.

float : Float -> Float -> Generator Float

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 1

Data Structure Generators

pair : Generator a -> Generator b -> Generator ( a, b )

Create 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)
list : Int -> Generator a -> Generator (List a)

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)

Custom Generators

constant : a -> Generator a

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.

map : (a -> b) -> Generator a -> Generator b

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)
map2 : (a -> b -> c) -> Generator a -> Generator b -> Generator c

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 genB
(a -> b -> c -> d)
-> Generator a
-> Generator b
-> Generator c
-> Generator d

Combine 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)
(a -> b -> c -> d -> e)
-> Generator a
-> Generator b
-> Generator c
-> Generator d
-> Generator e

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)
(a -> b -> c -> d -> e -> f)
-> Generator a
-> Generator b
-> Generator c
-> Generator d
-> Generator e
-> Generator f

Combine five generators.

andMap : Generator (a -> b) -> Generator a -> Generator b

Map over any number of generators.

randomPerson =
  person `map` genFirstName
    `andMap` genLastName
    `andMap` genBirthday
    `andMap` genPhoneNumber
andThen : Generator a -> (a -> Generator b) -> Generator b

Chain 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 genLowercaseLetter

If you find yourself calling constant in every branch of the callback, you can probably use map instead.

filter : (a -> Bool) -> Generator a -> Generator a

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)

Working With Seeds

initialSeed : Int -> Seed

Like initialSeed2, but takes only one integer. Mostly for compatibility with core.

split : Seed -> ( Seed, Seed )

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.

fastForward : Int -> Seed -> Seed

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) |> fst
type Seed

A Seed is the source of randomness in the whole system. It hides the current state of the random number generator.

Constants

maxInt : Int

The maximum value for randomly generated 32-bit ints.

minInt : Int

The minimum value for randomly generated 32-bit ints.