mgold/elm-random-pcg/1.0.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 across many frames, you should store the most recent seed in your model. If you need several independent models, you can split seeds into more seeds.

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.

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.

seed0 = initialSeed2 227852860 1498709020
(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 use the same seed, you get the same results.

(x, seed1) = generate (int 0 100) seed0
(y, seed2) = generate (int 0 100) seed0
(z, seed3) = generate (int 0 100) seed0
[x,y,z] -- [85, 85, 85]

The rest of the library is how to make generators for any kind of value you like, and how to create and manage random seeds.

Basic Generators

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.

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 (int 0 360)) (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.rgb (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

Seeds

type Seed

A Seed is the source of randomness in the whole system. Whenever you want to use a generator, you need to supply a seed. You will get back a new seed, which you must use to generate new random numbers. If you use the same seed many times, it will result in the same thing every time!

Seeds are created by providing initial values, which should happen only once per program. Although this library is more forgiving of poor seed choice than core, the best random seeds are drawn uniformly at random from the space of possible seeds. You can get a good starting value by running Math.floor(Math.random()*0xFFFFFFFF) in a JavaScript console.

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.

One can generate values at random and copy them to create a reproducible random generator.

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


-- Elm
seed0 : Seed
seed0 = initialSeed2 227852860 1498709020

Alternatively, we can generate the randomized values dynamically and pass them through a port. Out 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)] })
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. Splitting is a reproducible operation; just like generating numbers, it will be the same every time.

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)

Constants

maxInt : Int

The maximum value for randomly generated 32-bit ints.

minInt : Int

The minimum value for randomly generated 32-bit ints.